├── .appveyor.yml ├── .dir-locals.el ├── .git-blame-ignore-revs ├── .github ├── ISSUE_TEMPLATE │ └── issue-report.md └── workflows │ ├── build-macos-latest.yml │ └── build-ubuntu.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── AUTHORS ├── COPYING ├── ChangeLog.rst ├── README.md ├── cache.c ├── cache.h ├── compat ├── darwin_compat.c ├── darwin_compat.h ├── fuse_opt.c └── fuse_opt.h ├── justfile ├── make_release_tarball.sh ├── meson.build ├── sshfs.c ├── sshfs.rst ├── test ├── .gitignore ├── appveyor-build.sh ├── conftest.py ├── lint.sh ├── lsan_suppress.txt ├── meson.build ├── pytest.ini ├── test_sshfs.py ├── travis-build.sh ├── travis-install.sh ├── util.py └── wrong_command.c └── utils └── install_helper.sh /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | 3 | install: 4 | 5 | # install WinFsp 6 | - appveyor DownloadFile https://github.com/billziss-gh/winfsp/releases/download/v1.4B2/winfsp-1.4.18211.msi 7 | - for %%f in ("winfsp-*.msi") do start /wait msiexec /i %%f /qn INSTALLLEVEL=1000 8 | 9 | # install FUSE for Cygwin (64-bit and 32-bit) 10 | - C:\cygwin64\bin\env.exe -i PATH=/bin bash "%ProgramFiles(x86)%\WinFsp\opt\cygfuse\install.sh" 11 | - C:\cygwin\bin\env.exe -i PATH=/bin bash "%ProgramFiles(x86)%\WinFsp\opt\cygfuse\install.sh" 12 | 13 | # install additional Cygwin packages (64-bit and 32-bit) 14 | - C:\cygwin64\setup-x86_64.exe -qnNdO -R C:\cygwin64 -s http://cygwin.mirror.constant.com -l C:\cygwin64\var\cache\setup -P libglib2.0-devel -P meson 15 | - C:\cygwin\setup-x86.exe -qnNdO -R C:\cygwin -s http://cygwin.mirror.constant.com -l C:\cygwin\var\cache\setup -P libglib2.0-devel -P meson 16 | 17 | build_script: 18 | - C:\cygwin64\bin\env.exe -i PATH=/bin bash test\appveyor-build.sh 19 | - C:\cygwin\bin\env.exe -i PATH=/bin bash test\appveyor-build.sh 20 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((python-mode . ((indent-tabs-mode . nil))) 2 | (autoconf-mode . ((indent-tabs-mode . t))) 3 | (c-mode . ((c-file-style . "stroustrup") 4 | (indent-tabs-mode . t) 5 | (tab-width . 8) 6 | (c-basic-offset . 8) 7 | (c-file-offsets . 8 | ((block-close . 0) 9 | (brace-list-close . 0) 10 | (brace-list-entry . 0) 11 | (brace-list-intro . +) 12 | (case-label . 0) 13 | (class-close . 0) 14 | (defun-block-intro . +) 15 | (defun-close . 0) 16 | (defun-open . 0) 17 | (else-clause . 0) 18 | (inclass . +) 19 | (label . 0) 20 | (statement . 0) 21 | (statement-block-intro . +) 22 | (statement-case-intro . +) 23 | (statement-cont . +) 24 | (substatement . +) 25 | (topmost-intro . 0)))))) 26 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | d54c7ecbd618afb4df524e0d96dec7fe7cc2935d 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | PLEASE READ BEFORE REPORTING AN ISSUE 11 | 12 | SSHFS does not have any active, regular contributors or developers. The current maintainer continues to apply pull requests and tries to make regular releases, but unfortunately has no capacity to do any development beyond addressing high-impact issues. When reporting bugs, please understand that unless you are including a pull request or are reporting a critical issue, you will probably not get a response. 13 | 14 | To prevent the issue tracker from being flooded with issues that no-one is intending to work on, and to give more visibility to critical issues that users should be aware of and that most urgently need attention, I will also close most bug reports once they've been inactive for a while. 15 | 16 | Please note that this isn't meant to imply that you haven't found a bug - you most likely have and I'm grateful that you took the time to report it. Unfortunately, SSHFS is a purely volunteer driven project, 17 | and at the moment there simply aren't any volunteers. 18 | -------------------------------------------------------------------------------- /.github/workflows/build-macos-latest.yml: -------------------------------------------------------------------------------- 1 | name: build-macos-latest 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 7 | BUILD_TYPE: Release 8 | 9 | 10 | jobs: 11 | build: 12 | # The CMake configure and build commands are platform agnostic and should work equally 13 | # well on Windows or Mac. You can convert this to a matrix build if you need 14 | # cross-platform coverage. 15 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 16 | runs-on: macos-latest 17 | strategy: 18 | matrix: 19 | compiler: [gcc, clang] 20 | include: 21 | - compiler: gcc 22 | cc: gcc 23 | cxx: g++ 24 | - compiler: clang 25 | cc: clang 26 | cxx: clang++ 27 | steps: 28 | - uses: actions/checkout@v2 29 | with: 30 | submodules: 'recursive' 31 | 32 | - name: Install libraries 33 | run: | 34 | checkPkgAndInstall() 35 | { 36 | while [ $# -ne 0 ] 37 | do 38 | rm -f '/usr/local/bin/2to3' 39 | if brew ls --versions $1 ; then 40 | brew upgrade $1 41 | else 42 | brew install $1 43 | fi 44 | shift 45 | done 46 | } 47 | checkPkgAndInstall ccache meson macfuse glib 48 | 49 | - name: Create Build Environment 50 | # Some projects don't allow in-source building, so create a separate build directory 51 | # We'll use this as our working directory for all subsequent commands 52 | run: mkdir ${{github.workspace}}/build 53 | 54 | - name: Configure meson 55 | # Use a bash shell so we can use the same syntax for environment variable 56 | # access regardless of the host operating system 57 | shell: bash 58 | working-directory: ${{github.workspace}}/build 59 | run: meson 60 | 61 | - name: Build 62 | working-directory: ${{github.workspace}}/build 63 | shell: bash 64 | run: ninja 65 | 66 | - name: upload build artifact 67 | uses: actions/upload-artifact@v3 68 | with: 69 | name: sshfs 70 | path: build/sshfs 71 | 72 | - name: Test 73 | working-directory: ${{github.workspace}}/build 74 | shell: bash 75 | run: | 76 | ssh-keygen -q -t ed25519 -C "your_email@example.com" -f "${HOME}/.ssh/id_ed25519" 77 | eval "$(ssh-agent -s)" 78 | ssh-add ~/.ssh/id_ed25519 79 | cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys 80 | mkdir -p "${HOME}/.ssh" 81 | echo "Host *" > "${HOME}/.ssh/config" 82 | echo " StrictHostKeyChecking no" >> "${HOME}/.ssh/config" 83 | chmod 400 "${HOME}/.ssh/config" 84 | python3 -m pytest test/ 85 | 86 | -------------------------------------------------------------------------------- /.github/workflows/build-ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: build and test 2 | on: 3 | push: 4 | 5 | pull_request: 6 | 7 | workflow_dispatch: # this is a nice option that will enable a button w/ inputs 8 | inputs: 9 | git-ref: 10 | description: Git Ref (Optional) 11 | required: false 12 | jobs: 13 | build-and-test: 14 | name: Build and test 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v3 19 | 20 | - uses: actions/setup-python@v4 21 | 22 | - name: Install build dependencies 23 | run: | 24 | sudo apt-get update 25 | sudo apt-get install valgrind gcc ninja-build meson libglib2.0-dev libfuse3-dev 26 | 27 | - name: Install meson 28 | run: pip3 install meson pytest 29 | 30 | - name: build 31 | run: | 32 | mkdir build; cd build 33 | meson .. 34 | ninja 35 | 36 | # cd does not persist across steps 37 | - name: upload build artifact 38 | uses: actions/upload-artifact@v3 39 | with: 40 | name: sshfs 41 | path: build/sshfs 42 | 43 | - name: make ssh into localhost without prompt possible for tests 44 | run: | 45 | ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N "" 46 | cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys 47 | 48 | - name: run tests 49 | run: | 50 | cd build 51 | python3 -m pytest test/ 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE! Don't add files that are generated in specific 3 | # subdirectories here. Add them in the ".gitignore" file 4 | # in that subdirectory instead. 5 | # 6 | # NOTE! Please use 'git ls-files -i --exclude-standard' 7 | # command after changing this file, to see if there are 8 | # any tracked files which get ignored after the change. 9 | *.o 10 | *.lo 11 | *.la 12 | *.gz 13 | \#*# 14 | *.orig 15 | *~ 16 | Makefile.in 17 | Makefile 18 | *.m4 19 | stamp-h* 20 | config.* 21 | sshfs.1 22 | /sshfs 23 | /ltmain.sh 24 | /configure 25 | /install-sh 26 | /mkinstalldirs 27 | /missing 28 | /*.cache 29 | /depcomp 30 | /compile 31 | /libtool 32 | /INSTALL 33 | /.pc 34 | /patches 35 | /m4 36 | .deps/ 37 | /build 38 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v4.0.1 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | - repo: https://github.com/jumanjihouse/pre-commit-hooks 12 | rev: 2.1.5 13 | hooks: 14 | - id: shellcheck 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: focal 2 | 3 | language: c 4 | 5 | cache: 6 | - pip 7 | 8 | addons: 9 | apt: 10 | packages: 11 | - shellcheck 12 | - valgrind 13 | - gcc 14 | - clang 15 | - python-docutils 16 | - python3-pip 17 | - python3-setuptools 18 | - ninja-build 19 | - meson 20 | - python3-pytest 21 | - libglib2.0-dev 22 | 23 | install: test/travis-install.sh 24 | 25 | jobs: 26 | include: 27 | - name: Lint 28 | script: ./test/lint.sh 29 | install: skip 30 | - name: Build + Test 31 | script: test/travis-build.sh 32 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Current Maintainer 2 | ------------------ 3 | 4 | None. 5 | 6 | 7 | Past Maintainers 8 | ---------------- 9 | 10 | Nikolaus Rath (until 05/2022) 11 | Miklos Szeredi (until 12/2015) 12 | 13 | 14 | Contributors (autogenerated list) 15 | --------------------------------- 16 | 17 | a1346054 <36859588+a1346054@users.noreply.github.com> 18 | Alan Jenkins 19 | Alexander Neumann 20 | Anatol Pomozov 21 | Andrew Stone 22 | Antonio Rojas 23 | Benjamin Fleischer 24 | Berserker 25 | Bill Zissimopoulos 26 | bjoe2k4 27 | Brandon Carter 28 | Cam Cope 29 | Chris Wolfe 30 | Clayton G. Hobbs 31 | Daniel Lublin 32 | Dominique Martinet 33 | DrDaveD <2129743+DrDaveD@users.noreply.github.com> 34 | Fabrice Fontaine 35 | gala 36 | Galen Getsov <4815620+ggetsov@users.noreply.github.com> 37 | George Vlahavas 38 | G.raud Meyer 39 | harrim4n 40 | Jakub Jelen 41 | jeg139 <54814784+jeg139@users.noreply.github.com> 42 | Josh Triplett 43 | Julio Merino 44 | Julio Merino 45 | Junichi Uekawa 46 | Junichi Uekawa 47 | kalvdans 48 | Kim Brose 49 | Matthew Berginski 50 | Michael Forney 51 | Mike Kelly 52 | Mike Salvatore 53 | Miklos Szeredi 54 | Miklos Szeredi 55 | mssalvatore 56 | Nikolaus Rath 57 | Percy Jahn 58 | Peter Belm 59 | Peter Wienemann 60 | Qais Patankar 61 | Quentin Rameau 62 | Reid Wagner 63 | Rian Hunter 64 | Rian Hunter 65 | Samuel Murray 66 | S. D. Cloudt 67 | Simon Arlott <70171+nomis@users.noreply.github.com> 68 | smheidrich 69 | sunwire <50745572+sunwire@users.noreply.github.com> 70 | Tim Harder 71 | Timo Savola 72 | tpoindessous 73 | Viktor Szakats 74 | Zoltan Kuscsik 75 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /ChangeLog.rst: -------------------------------------------------------------------------------- 1 | Release 3.7.4a (2023-09-22) 2 | -------------------------- 3 | * Revert: Support request size negotiation and increased throughput on high-latency connections 4 | due to user reported bug 5 | 6 | Release 3.7.4 (2023-09-14) 7 | -------------------------- 8 | 9 | * Support request size negotiation and increased throughput on high-latency connections 10 | * Supports connecting to vsock (7) via shfs -o vsock=CID:PORT 11 | * README in markdown instead of rst 12 | * Various test fixes 13 | 14 | 15 | Release 3.7.3 (2022-05-26) 16 | -------------------------- 17 | 18 | * Minor bugfixes. 19 | 20 | * This is the last release from the current maintainer. SSHFS is now no longer maintained 21 | or developed. Github issue tracking and pull requests have therefore been disabled. The 22 | mailing list (see below) is still available for use. 23 | 24 | If you would like to take over this project, you are welcome to do so. Please fork it 25 | and develop the fork for a while. Once there has been 6 months of reasonable activity, 26 | please contact Nikolaus@rath.org and I'll be happy to give you ownership of this 27 | repository or replace with a pointer to the fork. 28 | 29 | 30 | Release 3.7.2 (2021-06-08) 31 | -------------------------- 32 | 33 | * Added a secondary check so if a mkdir request fails with EPERM an access request will be 34 | tried - returning EEXIST if the access was successful. 35 | Fixes: https://github.com/libfuse/sshfs/issues/243 36 | 37 | 38 | Release 3.7.1 (2020-11-09) 39 | -------------------------- 40 | 41 | * Minor bugfixes. 42 | 43 | 44 | Release 3.7.0 (2020-01-03) 45 | -------------------------- 46 | 47 | * New max_conns option enables the use of multiple connections to improve responsiveness 48 | during large file transfers. Thanks to Timo Savola for doing most of the implementation 49 | work, and thanks to CEA.fr for sponsoring remaining bugfixes and cleanups! 50 | 51 | * The `buflimit` workaround is now disabled by default. The corresponding bug in OpenSSH 52 | has been fixed in 2007 53 | (cf. https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=365541#37), so this shouldn't be 54 | needed anymore. If you depend on this workaround, please let the SSHFS maintainers know, 55 | otherwise support for the workaround will be removed completely in a future version. 56 | 57 | 58 | Release 3.6.0 (2019-11-03) 59 | -------------------------- 60 | 61 | * Added "-o direct_io" option. 62 | This option disables the use of page cache in kernel. 63 | This is useful for example if the file size is not known before reading it. 64 | For example if you mount /proc dir from a remote host without the direct_io 65 | option, the read always will return zero bytes instead of actual data. 66 | * Added --verbose option. 67 | * Fixed a number of compiler warnings. 68 | * Improved performance under OS X. 69 | 70 | 71 | Release 3.5.2 (2019-04-13) 72 | -------------------------- 73 | 74 | * Fixed "-o idmap=user" to map both UID and GID on all OSs. 75 | * Fixed improper handling of sequential spaces spaces in "ssh_command" option 76 | 77 | Release 3.5.1 (2018-12-22) 78 | -------------------------- 79 | 80 | * Documentation updates 81 | * Build system updates 82 | * Added "BindInterface" as valid "-o" option. 83 | 84 | Release 3.5.0 (2018-08-28) 85 | -------------------------- 86 | 87 | * Fixed error code returned by rename(), allowing proper fallback. 88 | * Port to Cygwin. 89 | 90 | Release 3.4.0 (2018-06-29) 91 | -------------------------- 92 | 93 | * Make utimens(NULL) result in timestamp "now" -- no more touched files 94 | dated 1970-01-01 95 | * New `createmode` workaround. 96 | * Fix `fstat` workaround regression. 97 | 98 | Release 3.3.2 (2018-04-29) 99 | -------------------------- 100 | 101 | * New `renamexdev` workaround. 102 | 103 | Release 3.3.1 (2017-10-25) 104 | -------------------------- 105 | 106 | * Manpage is now installed in correct directory. 107 | * SSHFS now supports (or rather: ignores) some options that it may 108 | receive as result of being mounted from ``/etc/mtab``. This includes 109 | things like ``user``, ``netdev``, or ``auto``. 110 | 111 | SSHFS 3.3.0 (2017-09-20) 112 | ------------------------ 113 | 114 | * Dropped support for writeback caching (and, as a consequence, 115 | "unreliable append" operation). As of kernel 4.14, the FUSE module's 116 | writeback implementation is not compatible with network filesystems 117 | and there are no imminent plans to change that. 118 | * Add support for mounting from /etc/fstab 119 | * Dropped support for building with autotools. 120 | * Added missing options to man page. 121 | 122 | Release 3.2.0 (2017-08-06) 123 | -------------------------- 124 | 125 | * Re-enabled writeback cache. 126 | * SSHFS now supports O_APPEND. 127 | 128 | Release 3.1.0 (2017-08-04) 129 | -------------------------- 130 | 131 | * Temporarily disabled the writeback cache feature, since there 132 | have been reports of dataloss when appending to files when 133 | writeback caching is enabled. 134 | 135 | * Fixed a crash due to a race condition when listing 136 | directory contents. 137 | 138 | * For improved backwards compatibility, SSHFS now also silently 139 | accepts the old ``-o cache_*`` options. 140 | 141 | Release 3.0.0 (2017-07-08) 142 | -------------------------- 143 | 144 | * sshfs now requires libfuse 3.1.0 or newer. 145 | * When supported by the kernel, sshfs now uses writeback caching. 146 | * The `cache` option has been renamed to `dir_cache` for clarity. 147 | * Added unit tests 148 | * --debug now behaves like -o debug_sshfs, i.e. it enables sshfs 149 | debugging messages rather than libfuse debugging messages. 150 | * Documented limited hardlink support. 151 | * Added support for building with Meson. 152 | * Added support for more SSH options. 153 | * Dropped support for the *nodelay* workaround - the last OpenSSH 154 | version for which this was useful was released in 2006. 155 | * Dropped support for the *nodelaysrv* workaround. The same effect 156 | (enabling NODELAY on the server side *and* enabling X11 forwarding) 157 | can be achieved by explicitly passing `-o ForwardX11` 158 | * Removed support for `-o workaround=all`. Workarounds should always 159 | enabled explicitly and only when needed. There is no point in always 160 | enabling a potentially changing set of workarounds. 161 | 162 | Release 2.9 (2017-04-17) 163 | ------------------------ 164 | 165 | * Improved support for Cygwin. 166 | * Various small bugfixes. 167 | 168 | Release 2.8 (2016-06-22) 169 | ------------------------ 170 | 171 | * Added support for the "fsync" extension. 172 | * Fixed a build problem with bitbake 173 | 174 | Release 2.7 (2016-03-01) 175 | ------------------------ 176 | 177 | * Integrated osxfuse's copy of sshfs, which means that sshfs now works 178 | on OS X out of the box. 179 | * Added -o cache_max_size=N option to let users tune the maximum size of 180 | the cache in number of entries. 181 | * Added -o cache_clean_interval=N and -o cache_min_clean_interval=N 182 | options to let users tune the cleaning behavior of the cache. 183 | 184 | Release 2.6 (2015-01-28) 185 | ------------------------ 186 | 187 | * New maintainer (Nikolaus Rath ) 188 | 189 | Release 2.5 (2014-01-14) 190 | ------------------------ 191 | 192 | * Some performance improvements for large directories. 193 | * New `disable_hardlink` option. 194 | * Various small bugfixes. 195 | 196 | Release 2.4 (2012-03-08) 197 | ------------------------ 198 | 199 | * New `slave` option. 200 | * New `idmap`, `uidmap` and `gidmap` options. 201 | * Various small bugfixes. 202 | 203 | Release 2.3 (2011-07-01) 204 | ------------------------ 205 | 206 | * Support hard link creation if server is OpenSSH 5.7 or later 207 | * Small improvements and bug fixes 208 | * Check mount point and options before connecting to ssh server 209 | * New 'delay_connect' option 210 | 211 | Release 2.2 (2008-10-20) 212 | ------------------------ 213 | 214 | * Handle numerical IPv6 addresses enclosed in square brackets 215 | * Handle commas in usernames 216 | 217 | Release 2.1 (2008-07-11) 218 | ------------------------ 219 | 220 | * Small improvements and bug fixes 221 | 222 | Release 2.0 (2008-04-23) 223 | ------------------------ 224 | 225 | * Support password authentication with pam_mount 226 | 227 | * Support atomic renames if server is OpenSSH 4.9 or later 228 | 229 | * Support getting disk usage if server is OpenSSH 5.1 or later 230 | 231 | * Small enhancements and bug fixes 232 | 233 | What is new in 1.9 234 | ------------------ 235 | 236 | * Fix a serious bug, that could result in sshfs hanging, crashing, or 237 | reporting out-of-memory 238 | 239 | What is new in 1.8 240 | ------------------ 241 | 242 | * Bug fixes 243 | 244 | What is new in 1.7 245 | ------------------ 246 | 247 | * Tolerate servers which print a banner on login 248 | 249 | * Small improvements 250 | 251 | What is new in 1.6 252 | ------------------ 253 | 254 | * Workaround for missing truncate operation on old sftp servers 255 | 256 | * Bug fixes 257 | 258 | What is new in 1.5 259 | ------------------ 260 | 261 | * Improvements to read performance. Now both read and write 262 | throughput should be very close to 'scp' 263 | 264 | * If used with FUSE 2.6.0 or later, then perform better data caching. 265 | This should show dramatic speed improvements when a file is opened 266 | more than once 267 | 268 | * Bug fixes 269 | 270 | What is new in 1.4 271 | ------------------ 272 | 273 | * Updated to version 25 of libfuse API 274 | 275 | * This means that the 'cp' of readonly file to sshfs bug is finally 276 | solved (as long as using libfuse 2.5.0 or later *and* Linux 2.6.15 277 | or later) 278 | 279 | * Sshfs now works on FreeBSD 280 | 281 | * Added option to "transform" absolute symbolic links 282 | 283 | What is new in 1.3 284 | ------------------ 285 | 286 | * Add workaround for failure to rename to an existing file 287 | 288 | * Simple user ID mapping 289 | 290 | * Estimate disk usage of files based on size 291 | 292 | * Report "infinite" disk space 293 | 294 | * Bug fixes 295 | 296 | What is new in 1.2 297 | ------------------ 298 | 299 | * Better compatibility with different sftp servers 300 | 301 | * Automatic reconnect (optional) 302 | 303 | What is new in 1.1 304 | ------------------ 305 | 306 | * Performance improvements: 307 | 308 | - directory content caching 309 | 310 | - symlink caching 311 | 312 | - asynchronous writeback 313 | 314 | - readahead 315 | 316 | * Fixed '-p' option 317 | 318 | What is new in 1.0 319 | ------------------ 320 | 321 | * Initial release 322 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This maintenance fork is archived since SSHFS is no longer abandoned. 2 | 3 | Please use https://github.com/libfuse/sshfs instead 4 | 5 | # SSHFS 6 | 7 | 8 | ## About 9 | 10 | SSHFS allows you to mount a remote filesystem using SFTP. Most SSH 11 | servers support and enable this SFTP access by default, so SSHFS is 12 | very simple to use - there's nothing to do on the server-side. 13 | 14 | 15 | ## Development Status 16 | 17 | 18 | SSHFS is shipped by all major Linux distributions and has been in 19 | production use across a wide range of systems for many years. However, 20 | at present SSHFS does not have any active, regular contributors, and 21 | there are a number of known issues (see the [bugtracker](https://github.com/deadbeefsociety/sshfs/issues)). The current 22 | maintainer continues to apply pull requests and makes regular 23 | releases, but unfortunately has no capacity to do any development 24 | beyond addressing high-impact issues. When reporting bugs, please 25 | understand that unless you are including a pull request or are 26 | reporting a critical issue, you will probably not get a response. 27 | 28 | 29 | ## How to use 30 | 31 | 32 | Once sshfs is installed (see next section) running it is very simple: 33 | 34 | ``` 35 | sshfs [user@]hostname:[directory] mountpoint 36 | ``` 37 | 38 | It is recommended to run SSHFS as regular user (not as root). For 39 | this to work the mountpoint must be owned by the user. If username is 40 | omitted SSHFS will use the local username. If the directory is 41 | omitted, SSHFS will mount the (remote) home directory. If you need to 42 | enter a password sshfs will ask for it (actually it just runs ssh 43 | which asks for the password if needed). 44 | 45 | Also many ssh options can be specified (see the manual pages for 46 | *sftp(1)* and *ssh_config(5)*), including the remote port number 47 | (`-oport=PORT`) 48 | 49 | To unmount the filesystem: 50 | 51 | ``` 52 | fusermount -u mountpoint 53 | ``` 54 | 55 | On BSD and macOS, to unmount the filesystem: 56 | 57 | ``` 58 | umount mountpoint 59 | ``` 60 | 61 | ## Installation 62 | 63 | 64 | First, download the latest SSHFS release from 65 | https://github.com/deadbeefsociety/sshfs/releases. You also need [libfuse](http://github.com/libfuse/libfuse) 3.1.0 or newer (or a 66 | similar library that provides a libfuse3 compatible interface for your operating 67 | system). Finally, you need the [Glib](https://developer.gnome.org/glib/stable/) library with development headers (which should be 68 | available from your operating system's package manager). 69 | 70 | To build and install, we recommend to use [Meson](http://mesonbuild.com/) (version 0.38 or 71 | newer) and [Ninja](https://ninja-build.org/). After extracting the sshfs tarball, create a 72 | (temporary) build directory and run Meson: 73 | 74 | ``` 75 | $ mkdir build; cd build 76 | $ meson .. 77 | ``` 78 | 79 | Normally, the default build options will work fine. If you 80 | nevertheless want to adjust them, you can do so with the *mesonconf* 81 | command: 82 | 83 | ``` 84 | $ mesonconf # list options 85 | $ mesonconf -D strip=true # set an option 86 | ``` 87 | 88 | To build, test and install SSHFS, you then use Ninja (running the 89 | tests requires the [py.test](http://www.pytest.org/) Python module): 90 | 91 | ``` 92 | $ ninja 93 | $ python3 -m pytest test/ # optional, but recommended 94 | $ sudo ninja install 95 | ``` 96 | 97 | ## Getting Help 98 | 99 | 100 | If you need help, please ask on the 101 | mailing list (subscribe at 102 | https://lists.sourceforge.net/lists/listinfo/fuse-sshfs). 103 | 104 | Please report any bugs on the GitHub issue tracker at 105 | https://github.com/deadbeefsociety/sshfs/issues. 106 | 107 | ## Packaging Status 108 | 109 | 110 | 111 | Packaging status 112 | 113 | 114 | -------------------------------------------------------------------------------- /cache.c: -------------------------------------------------------------------------------- 1 | /* 2 | Caching file system proxy 3 | Copyright (C) 2004 Miklos Szeredi 4 | 5 | This program can be distributed under the terms of the GNU GPL. 6 | See the file COPYING. 7 | */ 8 | 9 | #include "cache.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define DEFAULT_CACHE_TIMEOUT_SECS 20 19 | #define DEFAULT_MAX_CACHE_SIZE 10000 20 | #define DEFAULT_CACHE_CLEAN_INTERVAL_SECS 60 21 | #define DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS 5 22 | 23 | struct cache { 24 | int on; 25 | unsigned int stat_timeout_secs; 26 | unsigned int dir_timeout_secs; 27 | unsigned int link_timeout_secs; 28 | unsigned int max_size; 29 | unsigned int clean_interval_secs; 30 | unsigned int min_clean_interval_secs; 31 | struct fuse_operations *next_oper; 32 | GHashTable *table; 33 | pthread_mutex_t lock; 34 | time_t last_cleaned; 35 | uint64_t write_ctr; 36 | }; 37 | 38 | static struct cache cache; 39 | 40 | struct node { 41 | struct stat stat; 42 | time_t stat_valid; 43 | char **dir; 44 | time_t dir_valid; 45 | char *link; 46 | time_t link_valid; 47 | time_t valid; 48 | }; 49 | 50 | struct readdir_handle { 51 | const char *path; 52 | void *buf; 53 | fuse_fill_dir_t filler; 54 | GPtrArray *dir; 55 | uint64_t wrctr; 56 | }; 57 | 58 | struct file_handle { 59 | /* Did we send an open request to the underlying fs? */ 60 | int is_open; 61 | 62 | /* If so, this will hold its handle */ 63 | unsigned long fs_fh; 64 | }; 65 | 66 | static void free_node(gpointer node_) 67 | { 68 | struct node *node = (struct node *) node_; 69 | g_strfreev(node->dir); 70 | g_free(node); 71 | } 72 | 73 | static int cache_clean_entry(void *key_, struct node *node, time_t *now) 74 | { 75 | (void) key_; 76 | if (*now > node->valid) 77 | return TRUE; 78 | else 79 | return FALSE; 80 | } 81 | 82 | static void cache_clean(void) 83 | { 84 | time_t now = time(NULL); 85 | if (now > cache.last_cleaned + cache.min_clean_interval_secs && 86 | (g_hash_table_size(cache.table) > cache.max_size || 87 | now > cache.last_cleaned + cache.clean_interval_secs)) { 88 | g_hash_table_foreach_remove(cache.table, 89 | (GHRFunc) cache_clean_entry, &now); 90 | cache.last_cleaned = now; 91 | } 92 | } 93 | 94 | static struct node *cache_lookup(const char *path) 95 | { 96 | return (struct node *) g_hash_table_lookup(cache.table, path); 97 | } 98 | 99 | static void cache_purge(const char *path) 100 | { 101 | g_hash_table_remove(cache.table, path); 102 | } 103 | 104 | static void cache_purge_parent(const char *path) 105 | { 106 | const char *s = strrchr(path, '/'); 107 | if (s) { 108 | if (s == path) 109 | g_hash_table_remove(cache.table, "/"); 110 | else { 111 | char *parent = g_strndup(path, s - path); 112 | cache_purge(parent); 113 | g_free(parent); 114 | } 115 | } 116 | } 117 | 118 | void cache_invalidate(const char *path) 119 | { 120 | pthread_mutex_lock(&cache.lock); 121 | cache_purge(path); 122 | pthread_mutex_unlock(&cache.lock); 123 | } 124 | 125 | static void cache_invalidate_write(const char *path) 126 | { 127 | pthread_mutex_lock(&cache.lock); 128 | cache_purge(path); 129 | cache.write_ctr++; 130 | pthread_mutex_unlock(&cache.lock); 131 | } 132 | 133 | static void cache_invalidate_dir(const char *path) 134 | { 135 | pthread_mutex_lock(&cache.lock); 136 | cache_purge(path); 137 | cache_purge_parent(path); 138 | pthread_mutex_unlock(&cache.lock); 139 | } 140 | 141 | static int cache_del_children(const char *key, void *val_, const char *path) 142 | { 143 | (void) val_; 144 | if (strncmp(key, path, strlen(path)) == 0) 145 | return TRUE; 146 | else 147 | return FALSE; 148 | } 149 | 150 | static void cache_do_rename(const char *from, const char *to) 151 | { 152 | pthread_mutex_lock(&cache.lock); 153 | g_hash_table_foreach_remove(cache.table, (GHRFunc) cache_del_children, 154 | (char *) from); 155 | cache_purge(from); 156 | cache_purge(to); 157 | cache_purge_parent(from); 158 | cache_purge_parent(to); 159 | pthread_mutex_unlock(&cache.lock); 160 | } 161 | 162 | static struct node *cache_get(const char *path) 163 | { 164 | struct node *node = cache_lookup(path); 165 | if (node == NULL) { 166 | char *pathcopy = g_strdup(path); 167 | node = g_new0(struct node, 1); 168 | g_hash_table_insert(cache.table, pathcopy, node); 169 | } 170 | return node; 171 | } 172 | 173 | void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr) 174 | { 175 | struct node *node; 176 | 177 | pthread_mutex_lock(&cache.lock); 178 | if (wrctr == cache.write_ctr) { 179 | node = cache_get(path); 180 | node->stat = *stbuf; 181 | node->stat_valid = time(NULL) + cache.stat_timeout_secs; 182 | if (node->stat_valid > node->valid) 183 | node->valid = node->stat_valid; 184 | cache_clean(); 185 | } 186 | pthread_mutex_unlock(&cache.lock); 187 | } 188 | 189 | static void cache_add_dir(const char *path, char **dir) 190 | { 191 | struct node *node; 192 | 193 | pthread_mutex_lock(&cache.lock); 194 | node = cache_get(path); 195 | g_strfreev(node->dir); 196 | node->dir = dir; 197 | node->dir_valid = time(NULL) + cache.dir_timeout_secs; 198 | if (node->dir_valid > node->valid) 199 | node->valid = node->dir_valid; 200 | cache_clean(); 201 | pthread_mutex_unlock(&cache.lock); 202 | } 203 | 204 | static size_t my_strnlen(const char *s, size_t maxsize) 205 | { 206 | const char *p; 207 | for (p = s; maxsize && *p; maxsize--, p++); 208 | return p - s; 209 | } 210 | 211 | static void cache_add_link(const char *path, const char *link, size_t size) 212 | { 213 | struct node *node; 214 | 215 | pthread_mutex_lock(&cache.lock); 216 | node = cache_get(path); 217 | g_free(node->link); 218 | node->link = g_strndup(link, my_strnlen(link, size-1)); 219 | node->link_valid = time(NULL) + cache.link_timeout_secs; 220 | if (node->link_valid > node->valid) 221 | node->valid = node->link_valid; 222 | cache_clean(); 223 | pthread_mutex_unlock(&cache.lock); 224 | } 225 | 226 | static int cache_get_attr(const char *path, struct stat *stbuf) 227 | { 228 | struct node *node; 229 | int err = -EAGAIN; 230 | pthread_mutex_lock(&cache.lock); 231 | node = cache_lookup(path); 232 | if (node != NULL) { 233 | time_t now = time(NULL); 234 | if (node->stat_valid - now >= 0) { 235 | *stbuf = node->stat; 236 | err = 0; 237 | } 238 | } 239 | pthread_mutex_unlock(&cache.lock); 240 | return err; 241 | } 242 | 243 | uint64_t cache_get_write_ctr(void) 244 | { 245 | uint64_t res; 246 | 247 | pthread_mutex_lock(&cache.lock); 248 | res = cache.write_ctr; 249 | pthread_mutex_unlock(&cache.lock); 250 | 251 | return res; 252 | } 253 | 254 | static void *cache_init(struct fuse_conn_info *conn, 255 | struct fuse_config *cfg) 256 | { 257 | void *res; 258 | res = cache.next_oper->init(conn, cfg); 259 | 260 | // Cache requires a path for each request 261 | cfg->nullpath_ok = 0; 262 | 263 | return res; 264 | } 265 | 266 | static int cache_getattr(const char *path, struct stat *stbuf, 267 | struct fuse_file_info *fi) 268 | { 269 | int err = cache_get_attr(path, stbuf); 270 | if (err) { 271 | uint64_t wrctr = cache_get_write_ctr(); 272 | err = cache.next_oper->getattr(path, stbuf, fi); 273 | if (!err) 274 | cache_add_attr(path, stbuf, wrctr); 275 | } 276 | return err; 277 | } 278 | 279 | static int cache_readlink(const char *path, char *buf, size_t size) 280 | { 281 | struct node *node; 282 | int err; 283 | 284 | pthread_mutex_lock(&cache.lock); 285 | node = cache_lookup(path); 286 | if (node != NULL) { 287 | time_t now = time(NULL); 288 | if (node->link_valid - now >= 0) { 289 | strncpy(buf, node->link, size-1); 290 | buf[size-1] = '\0'; 291 | pthread_mutex_unlock(&cache.lock); 292 | return 0; 293 | } 294 | } 295 | pthread_mutex_unlock(&cache.lock); 296 | err = cache.next_oper->readlink(path, buf, size); 297 | if (!err) 298 | cache_add_link(path, buf, size); 299 | 300 | return err; 301 | } 302 | 303 | 304 | static int cache_opendir(const char *path, struct fuse_file_info *fi) 305 | { 306 | (void) path; 307 | struct file_handle *cfi; 308 | 309 | cfi = malloc(sizeof(struct file_handle)); 310 | if(cfi == NULL) 311 | return -ENOMEM; 312 | cfi->is_open = 0; 313 | fi->fh = (unsigned long) cfi; 314 | return 0; 315 | } 316 | 317 | static int cache_releasedir(const char *path, struct fuse_file_info *fi) 318 | { 319 | int err; 320 | struct file_handle *cfi; 321 | 322 | cfi = (struct file_handle*) fi->fh; 323 | 324 | if(cfi->is_open) { 325 | fi->fh = cfi->fs_fh; 326 | err = cache.next_oper->releasedir(path, fi); 327 | } else 328 | err = 0; 329 | 330 | free(cfi); 331 | return err; 332 | } 333 | 334 | static int cache_dirfill (void *buf, const char *name, 335 | const struct stat *stbuf, off_t off, 336 | enum fuse_fill_dir_flags flags) 337 | { 338 | int err; 339 | struct readdir_handle *ch; 340 | 341 | ch = (struct readdir_handle*) buf; 342 | err = ch->filler(ch->buf, name, stbuf, off, flags); 343 | if (!err) { 344 | g_ptr_array_add(ch->dir, g_strdup(name)); 345 | if (stbuf->st_mode & S_IFMT) { 346 | char *fullpath; 347 | const char *basepath = !ch->path[1] ? "" : ch->path; 348 | 349 | fullpath = g_strdup_printf("%s/%s", basepath, name); 350 | cache_add_attr(fullpath, stbuf, ch->wrctr); 351 | g_free(fullpath); 352 | } 353 | } 354 | return err; 355 | } 356 | 357 | static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler, 358 | off_t offset, struct fuse_file_info *fi, 359 | enum fuse_readdir_flags flags) 360 | { 361 | struct readdir_handle ch; 362 | struct file_handle *cfi; 363 | int err; 364 | char **dir; 365 | struct node *node; 366 | 367 | assert(offset == 0); 368 | 369 | pthread_mutex_lock(&cache.lock); 370 | node = cache_lookup(path); 371 | if (node != NULL && node->dir != NULL) { 372 | time_t now = time(NULL); 373 | if (node->dir_valid - now >= 0) { 374 | for(dir = node->dir; *dir != NULL; dir++) 375 | // FIXME: What about st_mode? 376 | filler(buf, *dir, NULL, 0, 0); 377 | pthread_mutex_unlock(&cache.lock); 378 | return 0; 379 | } 380 | } 381 | pthread_mutex_unlock(&cache.lock); 382 | 383 | cfi = (struct file_handle*) fi->fh; 384 | if(cfi->is_open) 385 | fi->fh = cfi->fs_fh; 386 | else { 387 | if(cache.next_oper->opendir) { 388 | err = cache.next_oper->opendir(path, fi); 389 | if(err) 390 | return err; 391 | } 392 | cfi->is_open = 1; 393 | cfi->fs_fh = fi->fh; 394 | } 395 | 396 | ch.path = path; 397 | ch.buf = buf; 398 | ch.filler = filler; 399 | ch.dir = g_ptr_array_new(); 400 | ch.wrctr = cache_get_write_ctr(); 401 | err = cache.next_oper->readdir(path, &ch, cache_dirfill, offset, fi, flags); 402 | g_ptr_array_add(ch.dir, NULL); 403 | dir = (char **) ch.dir->pdata; 404 | if (!err) { 405 | cache_add_dir(path, dir); 406 | } else { 407 | g_strfreev(dir); 408 | } 409 | g_ptr_array_free(ch.dir, FALSE); 410 | 411 | return err; 412 | } 413 | 414 | static int cache_mknod(const char *path, mode_t mode, dev_t rdev) 415 | { 416 | int err = cache.next_oper->mknod(path, mode, rdev); 417 | if (!err) 418 | cache_invalidate_dir(path); 419 | return err; 420 | } 421 | 422 | static int cache_mkdir(const char *path, mode_t mode) 423 | { 424 | int err = cache.next_oper->mkdir(path, mode); 425 | if (!err) 426 | cache_invalidate_dir(path); 427 | return err; 428 | } 429 | 430 | static int cache_unlink(const char *path) 431 | { 432 | int err = cache.next_oper->unlink(path); 433 | if (!err) 434 | cache_invalidate_dir(path); 435 | return err; 436 | } 437 | 438 | static int cache_rmdir(const char *path) 439 | { 440 | int err = cache.next_oper->rmdir(path); 441 | if (!err) 442 | cache_invalidate_dir(path); 443 | return err; 444 | } 445 | 446 | static int cache_symlink(const char *from, const char *to) 447 | { 448 | int err = cache.next_oper->symlink(from, to); 449 | if (!err) 450 | cache_invalidate_dir(to); 451 | return err; 452 | } 453 | 454 | static int cache_rename(const char *from, const char *to, unsigned int flags) 455 | { 456 | int err = cache.next_oper->rename(from, to, flags); 457 | if (!err) 458 | cache_do_rename(from, to); 459 | return err; 460 | } 461 | 462 | static int cache_link(const char *from, const char *to) 463 | { 464 | int err = cache.next_oper->link(from, to); 465 | if (!err) { 466 | cache_invalidate(from); 467 | cache_invalidate_dir(to); 468 | } 469 | return err; 470 | } 471 | 472 | static int cache_chmod(const char *path, mode_t mode, 473 | struct fuse_file_info *fi) 474 | { 475 | int err = cache.next_oper->chmod(path, mode, fi); 476 | if (!err) 477 | cache_invalidate(path); 478 | return err; 479 | } 480 | 481 | static int cache_chown(const char *path, uid_t uid, gid_t gid, 482 | struct fuse_file_info *fi) 483 | { 484 | int err = cache.next_oper->chown(path, uid, gid, fi); 485 | if (!err) 486 | cache_invalidate(path); 487 | return err; 488 | } 489 | 490 | static int cache_utimens(const char *path, const struct timespec tv[2], 491 | struct fuse_file_info *fi) 492 | { 493 | int err = cache.next_oper->utimens(path, tv, fi); 494 | if (!err) 495 | cache_invalidate(path); 496 | return err; 497 | } 498 | 499 | static int cache_write(const char *path, const char *buf, size_t size, 500 | off_t offset, struct fuse_file_info *fi) 501 | { 502 | int res = cache.next_oper->write(path, buf, size, offset, fi); 503 | if (res >= 0) 504 | cache_invalidate_write(path); 505 | return res; 506 | } 507 | 508 | static int cache_create(const char *path, mode_t mode, 509 | struct fuse_file_info *fi) 510 | { 511 | int err = cache.next_oper->create(path, mode, fi); 512 | if (!err) 513 | cache_invalidate_dir(path); 514 | return err; 515 | } 516 | 517 | static int cache_truncate(const char *path, off_t size, 518 | struct fuse_file_info *fi) 519 | { 520 | int err = cache.next_oper->truncate(path, size, fi); 521 | if (!err) 522 | cache_invalidate(path); 523 | return err; 524 | } 525 | 526 | static void cache_fill(struct fuse_operations *oper, 527 | struct fuse_operations *cache_oper) 528 | { 529 | cache_oper->access = oper->access; 530 | cache_oper->chmod = oper->chmod ? cache_chmod : NULL; 531 | cache_oper->chown = oper->chown ? cache_chown : NULL; 532 | cache_oper->create = oper->create ? cache_create : NULL; 533 | cache_oper->flush = oper->flush; 534 | cache_oper->fsync = oper->fsync; 535 | cache_oper->getattr = oper->getattr ? cache_getattr : NULL; 536 | cache_oper->getxattr = oper->getxattr; 537 | cache_oper->init = cache_init; 538 | cache_oper->link = oper->link ? cache_link : NULL; 539 | cache_oper->listxattr = oper->listxattr; 540 | cache_oper->mkdir = oper->mkdir ? cache_mkdir : NULL; 541 | cache_oper->mknod = oper->mknod ? cache_mknod : NULL; 542 | cache_oper->open = oper->open; 543 | cache_oper->opendir = cache_opendir; 544 | cache_oper->read = oper->read; 545 | cache_oper->readdir = oper->readdir ? cache_readdir : NULL; 546 | cache_oper->readlink = oper->readlink ? cache_readlink : NULL; 547 | cache_oper->release = oper->release; 548 | cache_oper->releasedir = cache_releasedir; 549 | cache_oper->removexattr = oper->removexattr; 550 | cache_oper->rename = oper->rename ? cache_rename : NULL; 551 | cache_oper->rmdir = oper->rmdir ? cache_rmdir : NULL; 552 | cache_oper->setxattr = oper->setxattr; 553 | cache_oper->statfs = oper->statfs; 554 | cache_oper->symlink = oper->symlink ? cache_symlink : NULL; 555 | cache_oper->truncate = oper->truncate ? cache_truncate : NULL; 556 | cache_oper->unlink = oper->unlink ? cache_unlink : NULL; 557 | cache_oper->utimens = oper->utimens ? cache_utimens : NULL; 558 | cache_oper->write = oper->write ? cache_write : NULL; 559 | } 560 | 561 | struct fuse_operations *cache_wrap(struct fuse_operations *oper) 562 | { 563 | static struct fuse_operations cache_oper; 564 | cache.next_oper = oper; 565 | 566 | cache_fill(oper, &cache_oper); 567 | pthread_mutex_init(&cache.lock, NULL); 568 | cache.table = g_hash_table_new_full(g_str_hash, g_str_equal, 569 | g_free, free_node); 570 | if (cache.table == NULL) { 571 | fprintf(stderr, "failed to create cache\n"); 572 | return NULL; 573 | } 574 | return &cache_oper; 575 | } 576 | 577 | static const struct fuse_opt cache_opts[] = { 578 | { "dcache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 }, 579 | { "dcache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 }, 580 | { "dcache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 }, 581 | { "dcache_stat_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 }, 582 | { "dcache_dir_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 }, 583 | { "dcache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 }, 584 | { "dcache_max_size=%u", offsetof(struct cache, max_size), 0 }, 585 | { "dcache_clean_interval=%u", offsetof(struct cache, 586 | clean_interval_secs), 0 }, 587 | { "dcache_min_clean_interval=%u", offsetof(struct cache, 588 | min_clean_interval_secs), 0 }, 589 | 590 | /* For backwards compatibility */ 591 | { "cache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 }, 592 | { "cache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 }, 593 | { "cache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 }, 594 | { "cache_stat_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 }, 595 | { "cache_dir_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 }, 596 | { "cache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 }, 597 | { "cache_max_size=%u", offsetof(struct cache, max_size), 0 }, 598 | { "cache_clean_interval=%u", offsetof(struct cache, 599 | clean_interval_secs), 0 }, 600 | { "cache_min_clean_interval=%u", offsetof(struct cache, 601 | min_clean_interval_secs), 0 }, 602 | FUSE_OPT_END 603 | }; 604 | 605 | int cache_parse_options(struct fuse_args *args) 606 | { 607 | cache.stat_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS; 608 | cache.dir_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS; 609 | cache.link_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS; 610 | cache.max_size = DEFAULT_MAX_CACHE_SIZE; 611 | cache.clean_interval_secs = DEFAULT_CACHE_CLEAN_INTERVAL_SECS; 612 | cache.min_clean_interval_secs = DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS; 613 | 614 | return fuse_opt_parse(args, &cache, cache_opts, NULL); 615 | } 616 | -------------------------------------------------------------------------------- /cache.h: -------------------------------------------------------------------------------- 1 | /* 2 | Caching file system proxy 3 | Copyright (C) 2004 Miklos Szeredi 4 | 5 | This program can be distributed under the terms of the GNU GPL. 6 | See the file COPYING. 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | struct fuse_operations *cache_wrap(struct fuse_operations *oper); 13 | int cache_parse_options(struct fuse_args *args); 14 | void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr); 15 | void cache_invalidate(const char *path); 16 | uint64_t cache_get_write_ctr(void); 17 | -------------------------------------------------------------------------------- /compat/darwin_compat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006-2008 Amit Singh/Google Inc. 3 | * Copyright (c) 2012 Anatol Pomozov 4 | * Copyright (c) 2011-2013 Benjamin Fleischer 5 | */ 6 | 7 | #include "darwin_compat.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | /* 14 | * Semaphore implementation based on: 15 | * 16 | * Copyright (C) 2000,02 Free Software Foundation, Inc. 17 | * This file is part of the GNU C Library. 18 | * Written by Gal Le Mignot 19 | * 20 | * The GNU C Library is free software; you can redistribute it and/or 21 | * modify it under the terms of the GNU Library General Public License as 22 | * published by the Free Software Foundation; either version 2 of the 23 | * License, or (at your option) any later version. 24 | * 25 | * The GNU C Library is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 28 | * Library General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Library General Public 31 | * License along with the GNU C Library; see the file COPYING.LIB. If not, 32 | * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 33 | * Boston, MA 02111-1307, USA. 34 | */ 35 | 36 | /* Semaphores */ 37 | 38 | #define __SEM_ID_NONE ((int)0x0) 39 | #define __SEM_ID_LOCAL ((int)0xcafef00d) 40 | 41 | /* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_init.html */ 42 | int 43 | darwin_sem_init(darwin_sem_t *sem, int pshared, unsigned int value) 44 | { 45 | if (pshared) { 46 | errno = ENOSYS; 47 | return -1; 48 | } 49 | 50 | sem->id = __SEM_ID_NONE; 51 | 52 | if (pthread_cond_init(&sem->__data.local.count_cond, NULL)) { 53 | goto cond_init_fail; 54 | } 55 | 56 | if (pthread_mutex_init(&sem->__data.local.count_lock, NULL)) { 57 | goto mutex_init_fail; 58 | } 59 | 60 | sem->__data.local.count = value; 61 | sem->id = __SEM_ID_LOCAL; 62 | 63 | return 0; 64 | 65 | mutex_init_fail: 66 | 67 | pthread_cond_destroy(&sem->__data.local.count_cond); 68 | 69 | cond_init_fail: 70 | 71 | return -1; 72 | } 73 | 74 | /* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_destroy.html */ 75 | int 76 | darwin_sem_destroy(darwin_sem_t *sem) 77 | { 78 | int res = 0; 79 | 80 | pthread_mutex_lock(&sem->__data.local.count_lock); 81 | 82 | sem->id = __SEM_ID_NONE; 83 | pthread_cond_broadcast(&sem->__data.local.count_cond); 84 | 85 | if (pthread_cond_destroy(&sem->__data.local.count_cond)) { 86 | res = -1; 87 | } 88 | 89 | pthread_mutex_unlock(&sem->__data.local.count_lock); 90 | 91 | if (pthread_mutex_destroy(&sem->__data.local.count_lock)) { 92 | res = -1; 93 | } 94 | 95 | return res; 96 | } 97 | 98 | int 99 | darwin_sem_getvalue(darwin_sem_t *sem, unsigned int *sval) 100 | { 101 | int res = 0; 102 | 103 | pthread_mutex_lock(&sem->__data.local.count_lock); 104 | 105 | if (sem->id != __SEM_ID_LOCAL) { 106 | res = -1; 107 | errno = EINVAL; 108 | } else { 109 | *sval = sem->__data.local.count; 110 | } 111 | 112 | pthread_mutex_unlock(&sem->__data.local.count_lock); 113 | 114 | return res; 115 | } 116 | 117 | /* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_post.html */ 118 | int 119 | darwin_sem_post(darwin_sem_t *sem) 120 | { 121 | int res = 0; 122 | 123 | pthread_mutex_lock(&sem->__data.local.count_lock); 124 | 125 | if (sem->id != __SEM_ID_LOCAL) { 126 | res = -1; 127 | errno = EINVAL; 128 | } else if (sem->__data.local.count < DARWIN_SEM_VALUE_MAX) { 129 | sem->__data.local.count++; 130 | if (sem->__data.local.count == 1) { 131 | pthread_cond_signal(&sem->__data.local.count_cond); 132 | } 133 | } else { 134 | errno = ERANGE; 135 | res = -1; 136 | } 137 | 138 | pthread_mutex_unlock(&sem->__data.local.count_lock); 139 | 140 | return res; 141 | } 142 | 143 | /* http://www.opengroup.org/onlinepubs/009695399/functions/sem_timedwait.html */ 144 | int 145 | darwin_sem_timedwait(darwin_sem_t *sem, const struct timespec *abs_timeout) 146 | { 147 | int res = 0; 148 | 149 | if (abs_timeout && 150 | (abs_timeout->tv_nsec < 0 || abs_timeout->tv_nsec >= 1000000000)) { 151 | errno = EINVAL; 152 | return -1; 153 | } 154 | 155 | pthread_cleanup_push((void(*)(void*))&pthread_mutex_unlock, 156 | &sem->__data.local.count_lock); 157 | 158 | pthread_mutex_lock(&sem->__data.local.count_lock); 159 | 160 | if (sem->id != __SEM_ID_LOCAL) { 161 | errno = EINVAL; 162 | res = -1; 163 | } else { 164 | if (!sem->__data.local.count) { 165 | res = pthread_cond_timedwait(&sem->__data.local.count_cond, 166 | &sem->__data.local.count_lock, 167 | abs_timeout); 168 | } 169 | if (res) { 170 | assert(res == ETIMEDOUT); 171 | res = -1; 172 | errno = ETIMEDOUT; 173 | } else if (sem->id != __SEM_ID_LOCAL) { 174 | res = -1; 175 | errno = EINVAL; 176 | } else { 177 | sem->__data.local.count--; 178 | } 179 | } 180 | 181 | pthread_cleanup_pop(1); 182 | 183 | return res; 184 | } 185 | 186 | /* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_trywait.html */ 187 | int 188 | darwin_sem_trywait(darwin_sem_t *sem) 189 | { 190 | int res = 0; 191 | 192 | pthread_mutex_lock(&sem->__data.local.count_lock); 193 | 194 | if (sem->id != __SEM_ID_LOCAL) { 195 | res = -1; 196 | errno = EINVAL; 197 | } else if (sem->__data.local.count) { 198 | sem->__data.local.count--; 199 | } else { 200 | res = -1; 201 | errno = EAGAIN; 202 | } 203 | 204 | pthread_mutex_unlock (&sem->__data.local.count_lock); 205 | 206 | return res; 207 | } 208 | 209 | /* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_wait.html */ 210 | int 211 | darwin_sem_wait(darwin_sem_t *sem) 212 | { 213 | int res = 0; 214 | 215 | pthread_cleanup_push((void(*)(void*))&pthread_mutex_unlock, 216 | &sem->__data.local.count_lock); 217 | 218 | pthread_mutex_lock(&sem->__data.local.count_lock); 219 | 220 | if (sem->id != __SEM_ID_LOCAL) { 221 | errno = EINVAL; 222 | res = -1; 223 | } else { 224 | if (!sem->__data.local.count) { 225 | pthread_cond_wait(&sem->__data.local.count_cond, 226 | &sem->__data.local.count_lock); 227 | if (!sem->__data.local.count) { 228 | /* spurious wakeup, assume it is an interruption */ 229 | res = -1; 230 | errno = EINTR; 231 | goto out; 232 | } 233 | } 234 | if (sem->id != __SEM_ID_LOCAL) { 235 | res = -1; 236 | errno = EINVAL; 237 | } else { 238 | sem->__data.local.count--; 239 | } 240 | } 241 | 242 | out: 243 | pthread_cleanup_pop(1); 244 | 245 | return res; 246 | } 247 | -------------------------------------------------------------------------------- /compat/darwin_compat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006-2008 Amit Singh/Google Inc. 3 | * Copyright (c) 2011-2013 Benjamin Fleischer 4 | */ 5 | 6 | #ifndef _DARWIN_COMPAT_ 7 | #define _DARWIN_COMPAT_ 8 | 9 | #include 10 | 11 | /* Semaphores */ 12 | 13 | typedef struct darwin_sem { 14 | int id; 15 | union { 16 | struct 17 | { 18 | unsigned int count; 19 | pthread_mutex_t count_lock; 20 | pthread_cond_t count_cond; 21 | } local; 22 | } __data; 23 | } darwin_sem_t; 24 | 25 | #define DARWIN_SEM_VALUE_MAX ((int32_t)32767) 26 | 27 | int darwin_sem_init(darwin_sem_t *sem, int pshared, unsigned int value); 28 | int darwin_sem_destroy(darwin_sem_t *sem); 29 | int darwin_sem_getvalue(darwin_sem_t *sem, unsigned int *value); 30 | int darwin_sem_post(darwin_sem_t *sem); 31 | int darwin_sem_timedwait(darwin_sem_t *sem, const struct timespec *abs_timeout); 32 | int darwin_sem_trywait(darwin_sem_t *sem); 33 | int darwin_sem_wait(darwin_sem_t *sem); 34 | 35 | /* Caller must not include */ 36 | 37 | typedef darwin_sem_t sem_t; 38 | 39 | #define sem_init(s, p, v) darwin_sem_init(s, p, v) 40 | #define sem_destroy(s) darwin_sem_destroy(s) 41 | #define sem_getvalue(s, v) darwin_sem_getvalue(s, v) 42 | #define sem_post(s) darwin_sem_post(s) 43 | #define sem_timedwait(s, t) darwin_sem_timedwait(s, t) 44 | #define sem_trywait(s) darwin_sem_trywait(s) 45 | #define sem_wait(s) darwin_sem_wait(s) 46 | 47 | #define SEM_VALUE_MAX DARWIN_SEM_VALUE_MAX 48 | 49 | #endif /* _DARWIN_COMPAT_ */ 50 | -------------------------------------------------------------------------------- /compat/fuse_opt.c: -------------------------------------------------------------------------------- 1 | /* 2 | FUSE: Filesystem in Userspace 3 | Copyright (C) 2001-2006 Miklos Szeredi 4 | 5 | This program can be distributed under the terms of the GNU LGPL. 6 | See the file COPYING.LIB 7 | */ 8 | 9 | #include "fuse_opt.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | struct fuse_opt_context { 17 | void *data; 18 | const struct fuse_opt *opt; 19 | fuse_opt_proc_t proc; 20 | int argctr; 21 | int argc; 22 | char **argv; 23 | struct fuse_args outargs; 24 | char *opts; 25 | int nonopt; 26 | }; 27 | 28 | void fuse_opt_free_args(struct fuse_args *args) 29 | { 30 | if (args && args->argv && args->allocated) { 31 | int i; 32 | for (i = 0; i < args->argc; i++) 33 | free(args->argv[i]); 34 | free(args->argv); 35 | args->argv = NULL; 36 | args->allocated = 0; 37 | } 38 | } 39 | 40 | static int alloc_failed(void) 41 | { 42 | fprintf(stderr, "fuse: memory allocation failed\n"); 43 | return -1; 44 | } 45 | 46 | int fuse_opt_add_arg(struct fuse_args *args, const char *arg) 47 | { 48 | char **newargv; 49 | char *newarg; 50 | 51 | assert(!args->argv || args->allocated); 52 | 53 | newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *)); 54 | newarg = newargv ? strdup(arg) : NULL; 55 | if (!newargv || !newarg) 56 | return alloc_failed(); 57 | 58 | args->argv = newargv; 59 | args->allocated = 1; 60 | args->argv[args->argc++] = newarg; 61 | args->argv[args->argc] = NULL; 62 | return 0; 63 | } 64 | 65 | int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg) 66 | { 67 | assert(pos <= args->argc); 68 | if (fuse_opt_add_arg(args, arg) == -1) 69 | return -1; 70 | 71 | if (pos != args->argc - 1) { 72 | char *newarg = args->argv[args->argc - 1]; 73 | memmove(&args->argv[pos + 1], &args->argv[pos], 74 | sizeof(char *) * (args->argc - pos - 1)); 75 | args->argv[pos] = newarg; 76 | } 77 | return 0; 78 | } 79 | 80 | static int next_arg(struct fuse_opt_context *ctx, const char *opt) 81 | { 82 | if (ctx->argctr + 1 >= ctx->argc) { 83 | fprintf(stderr, "fuse: missing argument after `%s'\n", opt); 84 | return -1; 85 | } 86 | ctx->argctr++; 87 | return 0; 88 | } 89 | 90 | static int add_arg(struct fuse_opt_context *ctx, const char *arg) 91 | { 92 | return fuse_opt_add_arg(&ctx->outargs, arg); 93 | } 94 | 95 | int fuse_opt_add_opt(char **opts, const char *opt) 96 | { 97 | char *newopts; 98 | if (!*opts) 99 | newopts = strdup(opt); 100 | else { 101 | unsigned oldlen = strlen(*opts); 102 | newopts = realloc(*opts, oldlen + 1 + strlen(opt) + 1); 103 | if (newopts) { 104 | newopts[oldlen] = ','; 105 | strcpy(newopts + oldlen + 1, opt); 106 | } 107 | } 108 | if (!newopts) 109 | return alloc_failed(); 110 | 111 | *opts = newopts; 112 | return 0; 113 | } 114 | 115 | static int add_opt(struct fuse_opt_context *ctx, const char *opt) 116 | { 117 | return fuse_opt_add_opt(&ctx->opts, opt); 118 | } 119 | 120 | static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key, 121 | int iso) 122 | { 123 | if (key == FUSE_OPT_KEY_DISCARD) 124 | return 0; 125 | 126 | if (key != FUSE_OPT_KEY_KEEP && ctx->proc) { 127 | int res = ctx->proc(ctx->data, arg, key, &ctx->outargs); 128 | if (res == -1 || !res) 129 | return res; 130 | } 131 | if (iso) 132 | return add_opt(ctx, arg); 133 | else 134 | return add_arg(ctx, arg); 135 | } 136 | 137 | static int match_template(const char *t, const char *arg, unsigned *sepp) 138 | { 139 | int arglen = strlen(arg); 140 | const char *sep = strchr(t, '='); 141 | sep = sep ? sep : strchr(t, ' '); 142 | if (sep && (!sep[1] || sep[1] == '%')) { 143 | int tlen = sep - t; 144 | if (sep[0] == '=') 145 | tlen ++; 146 | if (arglen >= tlen && strncmp(arg, t, tlen) == 0) { 147 | *sepp = sep - t; 148 | return 1; 149 | } 150 | } 151 | if (strcmp(t, arg) == 0) { 152 | *sepp = 0; 153 | return 1; 154 | } 155 | return 0; 156 | } 157 | 158 | static const struct fuse_opt *find_opt(const struct fuse_opt *opt, 159 | const char *arg, unsigned *sepp) 160 | { 161 | for (; opt && opt->templ; opt++) 162 | if (match_template(opt->templ, arg, sepp)) 163 | return opt; 164 | return NULL; 165 | } 166 | 167 | int fuse_opt_match(const struct fuse_opt *opts, const char *opt) 168 | { 169 | unsigned dummy; 170 | return find_opt(opts, opt, &dummy) ? 1 : 0; 171 | } 172 | 173 | static int process_opt_param(void *var, const char *format, const char *param, 174 | const char *arg) 175 | { 176 | assert(format[0] == '%'); 177 | if (format[1] == 's') { 178 | char *copy = strdup(param); 179 | if (!copy) 180 | return alloc_failed(); 181 | 182 | *(char **) var = copy; 183 | } else { 184 | if (sscanf(param, format, var) != 1) { 185 | fprintf(stderr, "fuse: invalid parameter in option `%s'\n", arg); 186 | return -1; 187 | } 188 | } 189 | return 0; 190 | } 191 | 192 | static int process_opt(struct fuse_opt_context *ctx, 193 | const struct fuse_opt *opt, unsigned sep, 194 | const char *arg, int iso) 195 | { 196 | if (opt->offset == -1U) { 197 | if (call_proc(ctx, arg, opt->value, iso) == -1) 198 | return -1; 199 | } else { 200 | void *var = ctx->data + opt->offset; 201 | if (sep && opt->templ[sep + 1]) { 202 | const char *param = arg + sep; 203 | if (opt->templ[sep] == '=') 204 | param ++; 205 | if (process_opt_param(var, opt->templ + sep + 1, 206 | param, arg) == -1) 207 | return -1; 208 | } else 209 | *(int *)var = opt->value; 210 | } 211 | return 0; 212 | } 213 | 214 | static int process_opt_sep_arg(struct fuse_opt_context *ctx, 215 | const struct fuse_opt *opt, unsigned sep, 216 | const char *arg, int iso) 217 | { 218 | int res; 219 | char *newarg; 220 | char *param; 221 | 222 | if (next_arg(ctx, arg) == -1) 223 | return -1; 224 | 225 | param = ctx->argv[ctx->argctr]; 226 | newarg = malloc(sep + strlen(param) + 1); 227 | if (!newarg) 228 | return alloc_failed(); 229 | 230 | memcpy(newarg, arg, sep); 231 | strcpy(newarg + sep, param); 232 | res = process_opt(ctx, opt, sep, newarg, iso); 233 | free(newarg); 234 | 235 | return res; 236 | } 237 | 238 | static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso) 239 | { 240 | unsigned sep; 241 | const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep); 242 | if (opt) { 243 | for (; opt; opt = find_opt(opt + 1, arg, &sep)) { 244 | int res; 245 | if (sep && opt->templ[sep] == ' ' && !arg[sep]) 246 | res = process_opt_sep_arg(ctx, opt, sep, arg, iso); 247 | else 248 | res = process_opt(ctx, opt, sep, arg, iso); 249 | if (res == -1) 250 | return -1; 251 | } 252 | return 0; 253 | } else 254 | return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso); 255 | } 256 | 257 | static int process_real_option_group(struct fuse_opt_context *ctx, char *opts) 258 | { 259 | char *sep; 260 | 261 | do { 262 | int res; 263 | sep = strchr(opts, ','); 264 | if (sep) 265 | *sep = '\0'; 266 | res = process_gopt(ctx, opts, 1); 267 | if (res == -1) 268 | return -1; 269 | opts = sep + 1; 270 | } while (sep); 271 | 272 | return 0; 273 | } 274 | 275 | static int process_option_group(struct fuse_opt_context *ctx, const char *opts) 276 | { 277 | int res; 278 | char *copy; 279 | const char *sep = strchr(opts, ','); 280 | if (!sep) 281 | return process_gopt(ctx, opts, 1); 282 | 283 | copy = strdup(opts); 284 | if (!copy) { 285 | fprintf(stderr, "fuse: memory allocation failed\n"); 286 | return -1; 287 | } 288 | res = process_real_option_group(ctx, copy); 289 | free(copy); 290 | return res; 291 | } 292 | 293 | static int process_one(struct fuse_opt_context *ctx, const char *arg) 294 | { 295 | if (ctx->nonopt || arg[0] != '-') 296 | return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0); 297 | else if (arg[1] == 'o') { 298 | if (arg[2]) 299 | return process_option_group(ctx, arg + 2); 300 | else { 301 | if (next_arg(ctx, arg) == -1) 302 | return -1; 303 | 304 | return process_option_group(ctx, ctx->argv[ctx->argctr]); 305 | } 306 | } else if (arg[1] == '-' && !arg[2]) { 307 | if (add_arg(ctx, arg) == -1) 308 | return -1; 309 | ctx->nonopt = ctx->outargs.argc; 310 | return 0; 311 | } else 312 | return process_gopt(ctx, arg, 0); 313 | } 314 | 315 | static int opt_parse(struct fuse_opt_context *ctx) 316 | { 317 | if (ctx->argc) { 318 | if (add_arg(ctx, ctx->argv[0]) == -1) 319 | return -1; 320 | } 321 | 322 | for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++) 323 | if (process_one(ctx, ctx->argv[ctx->argctr]) == -1) 324 | return -1; 325 | 326 | if (ctx->opts) { 327 | if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 || 328 | fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1) 329 | return -1; 330 | } 331 | if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc) { 332 | free(ctx->outargs.argv[ctx->outargs.argc - 1]); 333 | ctx->outargs.argv[--ctx->outargs.argc] = NULL; 334 | } 335 | 336 | return 0; 337 | } 338 | 339 | int fuse_opt_parse(struct fuse_args *args, void *data, 340 | const struct fuse_opt opts[], fuse_opt_proc_t proc) 341 | { 342 | int res; 343 | struct fuse_opt_context ctx = { 344 | .data = data, 345 | .opt = opts, 346 | .proc = proc, 347 | }; 348 | 349 | if (!args || !args->argv || !args->argc) 350 | return 0; 351 | 352 | ctx.argc = args->argc; 353 | ctx.argv = args->argv; 354 | 355 | res = opt_parse(&ctx); 356 | if (res != -1) { 357 | struct fuse_args tmp = *args; 358 | *args = ctx.outargs; 359 | ctx.outargs = tmp; 360 | } 361 | free(ctx.opts); 362 | fuse_opt_free_args(&ctx.outargs); 363 | return res; 364 | } 365 | -------------------------------------------------------------------------------- /compat/fuse_opt.h: -------------------------------------------------------------------------------- 1 | /* 2 | FUSE: Filesystem in Userspace 3 | Copyright (C) 2001-2006 Miklos Szeredi 4 | 5 | This program can be distributed under the terms of the GNU GPL. 6 | See the file COPYING. 7 | */ 8 | 9 | #ifndef _FUSE_OPT_H_ 10 | #define _FUSE_OPT_H_ 11 | 12 | /* This file defines the option parsing interface of FUSE */ 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | /** 19 | * Option description 20 | * 21 | * This structure describes a single option, and an action associated 22 | * with it, in case it matches. 23 | * 24 | * More than one such match may occur, in which case the action for 25 | * each match is executed. 26 | * 27 | * There are three possible actions in case of a match: 28 | * 29 | * i) An integer (int or unsigned) variable determined by 'offset' is 30 | * set to 'value' 31 | * 32 | * ii) The processing function is called, with 'value' as the key 33 | * 34 | * iii) An integer (any) or string (char *) variable determined by 35 | * 'offset' is set to the value of an option parameter 36 | * 37 | * 'offset' should normally be either set to 38 | * 39 | * - 'offsetof(struct foo, member)' actions i) and iii) 40 | * 41 | * - -1 action ii) 42 | * 43 | * The 'offsetof()' macro is defined in the header. 44 | * 45 | * The template determines which options match, and also have an 46 | * effect on the action. Normally the action is either i) or ii), but 47 | * if a format is present in the template, then action iii) is 48 | * performed. 49 | * 50 | * The types of templates are: 51 | * 52 | * 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only 53 | * themselves. Invalid values are "--" and anything beginning 54 | * with "-o" 55 | * 56 | * 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or 57 | * the relevant option in a comma separated option list 58 | * 59 | * 3) "bar=", "--foo=", etc. These are variations of 1) and 2) 60 | * which have a parameter 61 | * 62 | * 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform 63 | * action iii). 64 | * 65 | * 5) "-x ", etc. Matches either "-xparam" or "-x param" as 66 | * two separate arguments 67 | * 68 | * 6) "-x %s", etc. Combination of 4) and 5) 69 | * 70 | * If the format is "%s", memory is allocated for the string unlike 71 | * with scanf(). 72 | */ 73 | struct fuse_opt { 74 | /** Matching template and optional parameter formatting */ 75 | const char *templ; 76 | 77 | /** 78 | * Offset of variable within 'data' parameter of fuse_opt_parse() 79 | * or -1 80 | */ 81 | unsigned long offset; 82 | 83 | /** 84 | * Value to set the variable to, or to be passed as 'key' to the 85 | * processing function. Ignored if template has a format 86 | */ 87 | int value; 88 | }; 89 | 90 | /** 91 | * Key option. In case of a match, the processing function will be 92 | * called with the specified key. 93 | */ 94 | #define FUSE_OPT_KEY(templ, key) { templ, -1U, key } 95 | 96 | /** 97 | * Last option. An array of 'struct fuse_opt' must end with a NULL 98 | * template value 99 | */ 100 | #define FUSE_OPT_END { .templ = NULL } 101 | 102 | /** 103 | * Argument list 104 | */ 105 | struct fuse_args { 106 | /** Argument count */ 107 | int argc; 108 | 109 | /** Argument vector. NULL terminated */ 110 | char **argv; 111 | 112 | /** Is 'argv' allocated? */ 113 | int allocated; 114 | }; 115 | 116 | /** 117 | * Initializer for 'struct fuse_args' 118 | */ 119 | #define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 } 120 | 121 | /** 122 | * Key value passed to the processing function if an option did not 123 | * match any template 124 | */ 125 | #define FUSE_OPT_KEY_OPT -1 126 | 127 | /** 128 | * Key value passed to the processing function for all non-options 129 | * 130 | * Non-options are the arguments beginning with a charater other than 131 | * '-' or all arguments after the special '--' option 132 | */ 133 | #define FUSE_OPT_KEY_NONOPT -2 134 | 135 | /** 136 | * Special key value for options to keep 137 | * 138 | * Argument is not passed to processing function, but behave as if the 139 | * processing function returned 1 140 | */ 141 | #define FUSE_OPT_KEY_KEEP -3 142 | 143 | /** 144 | * Special key value for options to discard 145 | * 146 | * Argument is not passed to processing function, but behave as if the 147 | * processing function returned zero 148 | */ 149 | #define FUSE_OPT_KEY_DISCARD -4 150 | 151 | /** 152 | * Processing function 153 | * 154 | * This function is called if 155 | * - option did not match any 'struct fuse_opt' 156 | * - argument is a non-option 157 | * - option did match and offset was set to -1 158 | * 159 | * The 'arg' parameter will always contain the whole argument or 160 | * option including the parameter if exists. A two-argument option 161 | * ("-x foo") is always converted to single arguemnt option of the 162 | * form "-xfoo" before this function is called. 163 | * 164 | * Options of the form '-ofoo' are passed to this function without the 165 | * '-o' prefix. 166 | * 167 | * The return value of this function determines whether this argument 168 | * is to be inserted into the output argument vector, or discarded. 169 | * 170 | * @param data is the user data passed to the fuse_opt_parse() function 171 | * @param arg is the whole argument or option 172 | * @param key determines why the processing function was called 173 | * @param outargs the current output argument list 174 | * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept 175 | */ 176 | typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, 177 | struct fuse_args *outargs); 178 | 179 | /** 180 | * Option parsing function 181 | * 182 | * If 'args' was returned from a previous call to fuse_opt_parse() or 183 | * it was constructed from 184 | * 185 | * A NULL 'args' is equivalent to an empty argument vector 186 | * 187 | * A NULL 'opts' is equivalent to an 'opts' array containing a single 188 | * end marker 189 | * 190 | * A NULL 'proc' is equivalent to a processing function always 191 | * returning '1' 192 | * 193 | * @param args is the input and output argument list 194 | * @param data is the user data 195 | * @param opts is the option description array 196 | * @param proc is the processing function 197 | * @return -1 on error, 0 on success 198 | */ 199 | int fuse_opt_parse(struct fuse_args *args, void *data, 200 | const struct fuse_opt opts[], fuse_opt_proc_t proc); 201 | 202 | /** 203 | * Add an option to a comma separated option list 204 | * 205 | * @param opts is a pointer to an option list, may point to a NULL value 206 | * @param opt is the option to add 207 | * @return -1 on allocation error, 0 on success 208 | */ 209 | int fuse_opt_add_opt(char **opts, const char *opt); 210 | 211 | /** 212 | * Add an argument to a NULL terminated argument vector 213 | * 214 | * @param args is the structure containing the current argument list 215 | * @param arg is the new argument to add 216 | * @return -1 on allocation error, 0 on success 217 | */ 218 | int fuse_opt_add_arg(struct fuse_args *args, const char *arg); 219 | 220 | /** 221 | * Add an argument at the specified position in a NULL terminated 222 | * argument vector 223 | * 224 | * Adds the argument to the N-th position. This is useful for adding 225 | * options at the beggining of the array which must not come after the 226 | * special '--' option. 227 | * 228 | * @param args is the structure containing the current argument list 229 | * @param pos is the position at which to add the argument 230 | * @param arg is the new argument to add 231 | * @return -1 on allocation error, 0 on success 232 | */ 233 | int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg); 234 | 235 | /** 236 | * Free the contents of argument list 237 | * 238 | * The structure itself is not freed 239 | * 240 | * @param args is the structure containing the argument list 241 | */ 242 | void fuse_opt_free_args(struct fuse_args *args); 243 | 244 | 245 | /** 246 | * Check if an option matches 247 | * 248 | * @param opts is the option description array 249 | * @param opt is the option to match 250 | * @return 1 if a match is found, 0 if not 251 | */ 252 | int fuse_opt_match(const struct fuse_opt opts[], const char *opt); 253 | 254 | #ifdef __cplusplus 255 | } 256 | #endif 257 | 258 | #endif /* _FUSE_OPT_H_ */ 259 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | default: 2 | just --list 3 | 4 | build_directory := "build" 5 | 6 | build: 7 | #!/usr/bin/env bash 8 | mkdir {{build_directory}} 9 | cd {{build_directory}} 10 | meson .. 11 | ninja 12 | 13 | 14 | test: build 15 | #!/usr/bin/env bash 16 | cd {{build_directory}} 17 | /usr/bin/env python3 -m pytest test 18 | 19 | -------------------------------------------------------------------------------- /make_release_tarball.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Create tarball from Git tag, removing and adding 4 | # some files. 5 | # 6 | 7 | set -e 8 | 9 | if [ -z "$1" ]; then 10 | TAG="$(git tag --list 'sshfs-3*' --sort=-taggerdate | head -1)" 11 | else 12 | TAG="$1" 13 | fi 14 | 15 | echo "Creating release tarball for ${TAG}..." 16 | 17 | mkdir "${TAG}" 18 | git archive --format=tar "${TAG}" | tar -x "--directory=${TAG}" 19 | find "${TAG}" -name .gitignore -delete 20 | rm "${TAG}/make_release_tarball.sh" \ 21 | "${TAG}/.travis.yml" \ 22 | "${TAG}/test/travis-build.sh" \ 23 | "${TAG}/test/travis-install.sh" 24 | tar -cJf "${TAG}.tar.xz" "${TAG}/" 25 | gpg --armor --detach-sign "${TAG}.tar.xz" 26 | 27 | PREV_TAG="$(git tag --list 'sshfs-3*' --sort=-taggerdate --merged "${TAG}^"| head -1)" 28 | echo "Contributors from ${PREV_TAG} to ${TAG}:" 29 | git log --pretty="format:%an <%aE>" "${PREV_TAG}..${TAG}" | sort -u 30 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('sshfs', 'c', version: '3.7.4a', 2 | meson_version: '>= 0.40', 3 | default_options: [ 'buildtype=debugoptimized' ]) 4 | 5 | add_global_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H', 6 | '-Wall', '-Wextra', '-Wno-sign-compare', 7 | '-Wmissing-declarations', '-Wwrite-strings', 8 | language: 'c') 9 | 10 | # Some (stupid) GCC versions warn about unused return values even when they are 11 | # casted to void. This makes -Wunused-result pretty useless, since there is no 12 | # way to suppress the warning when we really *want* to ignore the value. 13 | cc = meson.get_compiler('c') 14 | code = ''' 15 | __attribute__((warn_unused_result)) int get_4() { 16 | return 4; 17 | } 18 | int main(void) { 19 | (void) get_4(); 20 | return 0; 21 | }''' 22 | if not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ]) 23 | message('Compiler warns about unused result even when casting to void') 24 | add_global_arguments('-Wno-unused-result', language: 'c') 25 | endif 26 | 27 | 28 | rst2man = find_program('rst2man', 'rst2man.py', required: false) 29 | 30 | cfg = configuration_data() 31 | 32 | cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) 33 | 34 | include_dirs = [ include_directories('.') ] 35 | sshfs_sources = ['sshfs.c', 'cache.c'] 36 | if target_machine.system() == 'darwin' 37 | cfg.set_quoted('IDMAP_DEFAULT', 'user') 38 | sshfs_sources += [ 'compat/fuse_opt.c', 'compat/darwin_compat.c' ] 39 | include_dirs += [ include_directories('compat') ] 40 | else 41 | cfg.set_quoted('IDMAP_DEFAULT', 'none') 42 | endif 43 | 44 | configure_file(output: 'config.h', 45 | configuration : cfg) 46 | 47 | sshfs_deps = [ dependency('fuse3', version: '>= 3.1.0'), 48 | dependency('glib-2.0'), 49 | dependency('gthread-2.0') ] 50 | 51 | executable('sshfs', sshfs_sources, 52 | include_directories: include_dirs, 53 | dependencies: sshfs_deps, 54 | c_args: ['-DFUSE_USE_VERSION=31'], 55 | install: true, 56 | install_dir: get_option('bindir')) 57 | 58 | if rst2man.found() 59 | custom_target('manpages', input: [ 'sshfs.rst' ], output: [ 'sshfs.1' ], 60 | command: [rst2man, '@INPUT@', '@OUTPUT@'], install: true, 61 | install_dir: join_paths(get_option('mandir'), 'man1')) 62 | else 63 | message('rst2man not found, not building manual page.') 64 | endif 65 | 66 | meson.add_install_script('utils/install_helper.sh', 67 | get_option('sbindir'), 68 | get_option('bindir')) 69 | 70 | 71 | subdir('test') 72 | -------------------------------------------------------------------------------- /sshfs.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | SSHFS 3 | ======= 4 | 5 | --------------------------------------------- 6 | filesystem client based on SSH 7 | --------------------------------------------- 8 | 9 | :Manual section: 1 10 | :Manual group: User Commands 11 | 12 | Synopsis 13 | ======== 14 | 15 | To mount a filesystem:: 16 | 17 | sshfs [user@]host:[dir] mountpoint [options] 18 | 19 | If *host* is a numeric IPv6 address, it needs to be enclosed in square 20 | brackets. 21 | 22 | To unmount it:: 23 | 24 | fusermount3 -u mountpoint # Linux 25 | umount mountpoint # OS X, FreeBSD 26 | 27 | Description 28 | =========== 29 | 30 | SSHFS allows you to mount a remote filesystem using SSH (more precisely, the SFTP 31 | subsystem). Most SSH servers support and enable this SFTP access by default, so SSHFS is 32 | very simple to use - there's nothing to do on the server-side. 33 | 34 | By default, file permissions are ignored by SSHFS. Any user that can access the filesystem 35 | will be able to perform any operation that the remote server permits - based on the 36 | credentials that were used to connect to the server. If this is undesired, local 37 | permission checking can be enabled with ``-o default_permissions``. 38 | 39 | By default, only the mounting user will be able to access the filesystem. Access for other 40 | users can be enabled by passing ``-o allow_other``. In this case you most likely also 41 | want to use ``-o default_permissions``. 42 | 43 | It is recommended to run SSHFS as regular user (not as root). For this to work the 44 | mountpoint must be owned by the user. If username is omitted SSHFS will use the local 45 | username. If the directory is omitted, SSHFS will mount the (remote) home directory. If 46 | you need to enter a password sshfs will ask for it (actually it just runs ssh which ask 47 | for the password if needed). 48 | 49 | 50 | Options 51 | ======= 52 | 53 | 54 | -o opt,[opt...] 55 | mount options, see below for details. A a variety of SSH options can 56 | be given here as well, see the manual pages for *sftp(1)* and 57 | *ssh_config(5)*. 58 | 59 | -h, --help 60 | print help and exit. 61 | 62 | -V, --version 63 | print version information and exit. 64 | 65 | -d, --debug 66 | print debugging information. 67 | 68 | -p PORT 69 | equivalent to '-o port=PORT' 70 | 71 | -f 72 | do not daemonize, stay in foreground. 73 | 74 | -s 75 | Single threaded operation. 76 | 77 | -C 78 | equivalent to '-o compression=yes' 79 | 80 | -F ssh_configfile 81 | specifies alternative ssh configuration file 82 | 83 | -1 84 | equivalent to '-o ssh_protocol=1' 85 | 86 | -o reconnect 87 | automatically reconnect to server if connection is 88 | interrupted. Attempts to access files that were opened before the 89 | reconnection will give errors and need to be re-opened. 90 | 91 | -o delay_connect 92 | Don't immediately connect to server, wait until mountpoint is first 93 | accessed. 94 | 95 | -o sshfs_sync 96 | synchronous writes. This will slow things down, but may be useful 97 | in some situations. 98 | 99 | -o no_readahead 100 | Only read exactly the data that was requested, instead of 101 | speculatively reading more to anticipate the next read request. 102 | 103 | -o sync_readdir 104 | synchronous readdir. This will slow things down, but may be useful 105 | in some situations. 106 | 107 | -o workaround=LIST 108 | Enable the specified workaround. See the `Caveats` section below 109 | for some additional information. Possible values are: 110 | 111 | :rename: Emulate overwriting an existing file by deleting and 112 | renaming. 113 | :renamexdev: Make rename fail with EXDEV instead of the default EPERM 114 | to allow moving files across remote filesystems. 115 | :truncate: Work around servers that don't support truncate by 116 | coping the whole file, truncating it locally, and sending it 117 | back. 118 | :fstat: Work around broken servers that don't support *fstat()* by 119 | using *stat* instead. 120 | :buflimit: Work around OpenSSH "buffer fillup" bug. 121 | :createmode: Work around broken servers that produce an error when passing a 122 | non-zero mode to create, by always passing a mode of 0. 123 | 124 | -o idmap=TYPE 125 | How to map remote UID/GIDs to local values. Possible values are: 126 | 127 | :none: no translation of the ID space (default). 128 | 129 | :user: map the UID/GID of the remote user to UID/GID of the 130 | mounting user. 131 | 132 | :file: translate UIDs/GIDs based upon the contents of `--uidfile` 133 | and `--gidfile`. 134 | 135 | -o uidfile=FILE 136 | file containing ``username:uid`` mappings for `-o idmap=file` 137 | 138 | -o gidfile=FILE 139 | file containing ``groupname:gid`` mappings for `-o idmap=file` 140 | 141 | -o nomap=TYPE 142 | with idmap=file, how to handle missing mappings: 143 | 144 | :ignore: don't do any re-mapping 145 | :error: return an error (default) 146 | 147 | -o ssh_command=CMD 148 | execute CMD instead of 'ssh' 149 | 150 | -o ssh_protocol=N 151 | ssh protocol to use (default: 2) 152 | 153 | -o sftp_server=SERV 154 | path to sftp server or subsystem (default: sftp) 155 | 156 | -o directport=PORT 157 | directly connect to PORT bypassing ssh 158 | 159 | -o vsock=CID:PORT 160 | directly connect using a vsock to CID:PORT bypassing ssh 161 | 162 | -o passive 163 | communicate over stdin and stdout bypassing network. Useful for 164 | mounting local filesystem on the remote side. An example using 165 | dpipe command would be ``dpipe /usr/lib/openssh/sftp-server = ssh 166 | RemoteHostname sshfs :/directory/to/be/shared ~/mnt/src -o passive`` 167 | 168 | -o disable_hardlink 169 | With this option set, attempts to call `link(2)` will fail with 170 | error code ENOSYS. 171 | 172 | -o transform_symlinks 173 | transform absolute symlinks on remote side to relative 174 | symlinks. This means that if e.g. on the server side 175 | ``/foo/bar/com`` is a symlink to ``/foo/blub``, SSHFS will 176 | transform the link target to ``../blub`` on the client side. 177 | 178 | -o follow_symlinks 179 | follow symlinks on the server, i.e. present them as regular 180 | files on the client. If a symlink is dangling (i.e, the target does 181 | not exist) the behavior depends on the remote server - the entry 182 | may appear as a symlink on the client, or it may appear as a 183 | regular file that cannot be accessed. 184 | 185 | -o no_check_root 186 | don't check for existence of 'dir' on server 187 | 188 | -o password_stdin 189 | read password from stdin (only for pam_mount!) 190 | 191 | -o dir_cache=BOOL 192 | Enables (*yes*) or disables (*no*) the SSHFS directory cache. The 193 | directory cache holds the names of directory entries. Enabling it 194 | allows `readdir(3)` system calls to be processed without network 195 | access. 196 | 197 | -o dcache_max_size=N 198 | sets the maximum size of the directory cache. 199 | 200 | -o dcache_timeout=N 201 | sets timeout for directory cache in seconds. 202 | 203 | -o dcache_{stat,link,dir}_timeout=N 204 | sets separate timeout for {attributes, symlinks, names} in the 205 | directory cache. 206 | 207 | -o dcache_clean_interval=N 208 | sets the interval for automatic cleaning of the directory cache. 209 | 210 | -o dcache_min_clean_interval=N 211 | sets the interval for forced cleaning of the directory cache 212 | when full. 213 | 214 | -o direct_io 215 | This option disables the use of page cache (file content cache) in 216 | the kernel for this filesystem. 217 | This has several affects: 218 | 219 | 1. Each read() or write() system call will initiate one or more read or 220 | write operations, data will not be cached in the kernel. 221 | 222 | 2. The return value of the read() and write() system calls will correspond 223 | to the return values of the read and write operations. This is useful 224 | for example if the file size is not known in advance (before reading it). 225 | e.g. /proc filesystem 226 | 227 | -o max_conns=N 228 | sets the maximum number of simultaneous SSH connections 229 | to use. Each connection is established with a separate SSH process. 230 | The primary purpose of this feature is to improve the responsiveness of the 231 | file system during large file transfers. When using more than once 232 | connection, the *password_stdin* and *passive* options can not be 233 | used, and the *buflimit* workaround is not supported. 234 | 235 | In addition, SSHFS accepts several options common to all FUSE file 236 | systems. These are described in the `mount.fuse` manpage (look 237 | for "general", "libfuse specific", and "high-level API" options). 238 | 239 | Caveats / Workarounds 240 | ===================== 241 | 242 | Hardlinks 243 | ~~~~~~~~~ 244 | 245 | If the SSH server supports the *hardlinks* extension, SSHFS will allow 246 | you to create hardlinks. However, hardlinks will always appear as 247 | individual files when seen through an SSHFS mount, i.e. they will 248 | appear to have different inodes and an *st_nlink* value of 1. 249 | 250 | 251 | Rename 252 | ~~~~~~ 253 | 254 | Some SSH servers do not support atomically overwriting the destination 255 | when renaming a file. In this case you will get an error when you 256 | attempt to rename a file and the destination already exists. A 257 | workaround is to first remove the destination file, and then do the 258 | rename. SSHFS can do this automatically if you call it with `-o 259 | workaround=rename`. However, in this case it is still possible that 260 | someone (or something) recreates the destination file after SSHFS has 261 | removed it, but before SSHFS had the time to rename the old file. In 262 | this case, the rename will still fail. 263 | 264 | 265 | Permission denied when moving files across remote filesystems 266 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 267 | 268 | Most SFTP servers return only a generic "failure" when failing to rename 269 | across filesystem boundaries (EXDEV). sshfs normally converts this generic 270 | failure to a permission denied error (EPERM). If the option ``-o 271 | workaround=renamexdev`` is given, generic failures will be considered EXDEV 272 | errors which will make programs like `mv(1)` attempt to actually move the 273 | file after the failed rename. 274 | 275 | 276 | SSHFS hangs for no apparent reason 277 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 278 | 279 | In some cases, attempts to access the SSHFS mountpoint may freeze if 280 | no filesystem activity has occurred for some time. This is typically 281 | caused by the SSH connection being dropped because of inactivity 282 | without SSHFS being informed about that. As a workaround, you can try 283 | to mount with ``-o ServerAliveInterval=15``. This will force the SSH 284 | connection to stay alive even if you have no activity. 285 | 286 | 287 | SSHFS hangs after the connection was interrupted 288 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 289 | 290 | By default, network operations in SSHFS run without timeouts, mirroring the 291 | default behavior of SSH itself. As a consequence, if the connection to the 292 | remote host is interrupted (e.g. because a network cable was removed), 293 | operations on files or directories under the mountpoint will block until the 294 | connection is either restored or closed altogether (e.g. manually). 295 | Applications that try to access such files or directories will generally appear 296 | to "freeze" when this happens. 297 | 298 | If it is acceptable to discard data being read or written, a quick workaround 299 | is to kill the responsible ``sshfs`` process, which will make any blocking 300 | operations on the mounted filesystem error out and thereby "unfreeze" the 301 | relevant applications. Note that force unmounting with ``fusermount -zu``, on 302 | the other hand, does not help in this case and will leave read/write operations 303 | in the blocking state. 304 | 305 | For a more automatic solution, one can use the ``-o ServerAliveInterval=15`` 306 | option mentioned above, which will drop the connection after not receiving a 307 | response for 3 * 15 = 45 seconds from the remote host. By also supplying ``-o 308 | reconnect``, one can ensure that the connection is re-established as soon as 309 | possible afterwards. As before, this will naturally lead to loss of data that 310 | was in the process of being read or written at the time when the connection was 311 | interrupted. 312 | 313 | 314 | Mounting from /etc/fstab 315 | ======================== 316 | 317 | To mount an SSHFS filesystem from ``/etc/fstab``, simply use ``sshfs`` 318 | as the file system type. (For backwards compatibility, you may also 319 | use ``fuse.sshfs``). 320 | 321 | 322 | See also 323 | ======== 324 | 325 | The `mount.fuse(8)` manpage. 326 | 327 | Getting Help 328 | ============ 329 | 330 | If you need help, please ask on the 331 | mailing list (subscribe at 332 | https://lists.sourceforge.net/lists/listinfo/fuse-sshfs). 333 | 334 | Please report any bugs on the GitHub issue tracker at 335 | https://github.com/libfuse/libfuse/issues. 336 | 337 | 338 | Authors 339 | ======= 340 | 341 | SSHFS is currently maintained by Nikolaus Rath , 342 | and was created by Miklos Szeredi . 343 | 344 | This man page was originally written by Bartosz Fenski 345 | for the Debian GNU/Linux distribution (but it may 346 | be used by others). 347 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /test/appveyor-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | machine=$(uname -m) 5 | mkdir "build-$machine" 6 | cd "build-$machine" 7 | meson .. 8 | ninja 9 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pytest 3 | import time 4 | import re 5 | 6 | # If a test fails, wait a moment before retrieving the captured stdout/stderr. 7 | # When using a server process, this makes sure that we capture any potential 8 | # output of the server that comes *after* a test has failed. For example, if a 9 | # request handler raises an exception, the server first signals an error to 10 | # FUSE (causing the test to fail), and then logs the exception. Without the 11 | # extra delay, the exception will go into nowhere. 12 | 13 | 14 | @pytest.hookimpl(hookwrapper=True) 15 | def pytest_pyfunc_call(pyfuncitem): 16 | outcome = yield 17 | failed = outcome.excinfo is not None 18 | if failed: 19 | time.sleep(1) 20 | 21 | 22 | @pytest.fixture() 23 | def pass_capfd(request, capfd): 24 | """Provide capfd object to UnitTest instances""" 25 | request.instance.capfd = capfd 26 | 27 | 28 | def check_test_output(capfd): 29 | (stdout, stderr) = capfd.readouterr() 30 | 31 | # Write back what we've read (so that it will still be printed. 32 | sys.stdout.write(stdout) 33 | sys.stderr.write(stderr) 34 | 35 | # Strip out false positives 36 | for (pattern, flags, count) in capfd.false_positives: 37 | cp = re.compile(pattern, flags) 38 | (stdout, cnt) = cp.subn("", stdout, count=count) 39 | if count == 0 or count - cnt > 0: 40 | stderr = cp.sub("", stderr, count=count - cnt) 41 | 42 | patterns = [ 43 | r"\b{}\b".format(x) 44 | for x in ( 45 | "exception", 46 | "error", 47 | "warning", 48 | "fatal", 49 | "traceback", 50 | "fault", 51 | "crash(?:ed)?", 52 | "abort(?:ed)", 53 | "uninitiali[zs]ed", 54 | ) 55 | ] 56 | patterns += ["^==[0-9]+== "] 57 | for pattern in patterns: 58 | cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE) 59 | hit = cp.search(stderr) 60 | if hit: 61 | raise AssertionError( 62 | 'Suspicious output to stderr (matched "%s")' % hit.group(0) 63 | ) 64 | hit = cp.search(stdout) 65 | if hit: 66 | raise AssertionError( 67 | 'Suspicious output to stdout (matched "%s")' % hit.group(0) 68 | ) 69 | 70 | 71 | def register_output(self, pattern, count=1, flags=re.MULTILINE): 72 | """Register *pattern* as false positive for output checking 73 | 74 | This prevents the test from failing because the output otherwise 75 | appears suspicious. 76 | """ 77 | 78 | self.false_positives.append((pattern, flags, count)) 79 | 80 | 81 | # This is a terrible hack that allows us to access the fixtures from the 82 | # pytest_runtest_call hook. Among a lot of other hidden assumptions, it probably 83 | # relies on tests running sequential (i.e., don't dare to use e.g. the xdist 84 | # plugin) 85 | current_capfd = None 86 | 87 | 88 | @pytest.fixture(autouse=True) 89 | def save_cap_fixtures(request, capfd): 90 | global current_capfd 91 | capfd.false_positives = [] 92 | 93 | # Monkeypatch in a function to register false positives 94 | type(capfd).register_output = register_output 95 | 96 | if request.config.getoption("capture") == "no": 97 | capfd = None 98 | current_capfd = capfd 99 | bak = current_capfd 100 | yield 101 | 102 | # Try to catch problems with this hack (e.g. when running tests 103 | # simultaneously) 104 | assert bak is current_capfd 105 | current_capfd = None 106 | 107 | 108 | @pytest.hookimpl(trylast=True) 109 | def pytest_runtest_call(item): 110 | capfd = current_capfd 111 | if capfd is not None: 112 | check_test_output(capfd) 113 | -------------------------------------------------------------------------------- /test/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | pip3 install --user pre-commit 4 | pre-commit run --all-files --show-diff-on-failure 5 | -------------------------------------------------------------------------------- /test/lsan_suppress.txt: -------------------------------------------------------------------------------- 1 | # Suppression file for address sanitizer. 2 | 3 | # There are some leaks in command line option parsing. They should be 4 | # fixed at some point, but are harmless since the consume just a small, 5 | # constant amount of memory and do not grow. 6 | leak:fuse_opt_parse 7 | 8 | 9 | # Leaks in fusermount3 are harmless as well (it's a short-lived 10 | # process) - but patches are welcome! 11 | leak:fusermount.c 12 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | test_scripts = [ 'conftest.py', 'pytest.ini', 'test_sshfs.py', 2 | 'util.py' ] 3 | custom_target('test_scripts', input: test_scripts, 4 | output: test_scripts, build_by_default: true, 5 | command: ['cp', '-fPp', 6 | '@INPUT@', meson.current_build_dir() ]) 7 | 8 | # Provide something helpful when running 'ninja test' 9 | wrong_cmd = executable('wrong_command', 'wrong_command.c', 10 | install: false) 11 | test('wrong_cmd', wrong_cmd) 12 | -------------------------------------------------------------------------------- /test/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --verbose --assert=rewrite --tb=native -x -r a 3 | 4 | markers = uses_fuse: Mark to indicate that FUSE is available on the system running the test 5 | -------------------------------------------------------------------------------- /test/test_sshfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | if __name__ == "__main__": 4 | import pytest 5 | import sys 6 | 7 | sys.exit(pytest.main([__file__] + sys.argv[1:])) 8 | 9 | import subprocess 10 | import os 11 | import sys 12 | import pytest 13 | import stat 14 | import shutil 15 | import filecmp 16 | import errno 17 | from tempfile import NamedTemporaryFile 18 | from util import ( 19 | wait_for_mount, 20 | umount, 21 | cleanup, 22 | base_cmdline, 23 | basename, 24 | fuse_test_marker, 25 | safe_sleep, 26 | os_create, 27 | os_open, 28 | ) 29 | from os.path import join as pjoin 30 | 31 | TEST_FILE = __file__ 32 | 33 | pytestmark = fuse_test_marker() 34 | 35 | with open(TEST_FILE, "rb") as fh: 36 | TEST_DATA = fh.read() 37 | 38 | 39 | def name_generator(__ctr=[0]) -> str: 40 | """Generate a fresh filename on each call""" 41 | 42 | __ctr[0] += 1 43 | return f"testfile_{__ctr[0]}" 44 | 45 | 46 | @pytest.mark.parametrize( 47 | "debug", 48 | [pytest.param(False, id="debug=false"), pytest.param(True, id="debug=true")], 49 | ) 50 | @pytest.mark.parametrize( 51 | "cache_timeout", 52 | [pytest.param(0, id="cache_timeout=0"), pytest.param(1, id="cache_timeout=1")], 53 | ) 54 | @pytest.mark.parametrize( 55 | "sync_rd", 56 | [pytest.param(True, id="sync_rd=true"), pytest.param(False, id="sync_rd=false")], 57 | ) 58 | @pytest.mark.parametrize( 59 | "multiconn", 60 | [ 61 | pytest.param(True, id="multiconn=true"), 62 | pytest.param(False, id="multiconn=false"), 63 | ], 64 | ) 65 | def test_sshfs( 66 | tmpdir, debug: bool, cache_timeout: int, sync_rd: bool, multiconn: bool, capfd 67 | ) -> None: 68 | 69 | # Avoid false positives from debug messages 70 | # if debug: 71 | # capfd.register_output(r'^ unique: [0-9]+, error: -[0-9]+ .+$', 72 | # count=0) 73 | 74 | # Avoid false positives from storing key for localhost 75 | capfd.register_output(r"^Warning: Permanently added 'localhost' .+", count=0) 76 | 77 | # Test if we can ssh into localhost without password 78 | try: 79 | res = subprocess.call( 80 | [ 81 | "ssh", 82 | "-o", 83 | "StrictHostKeyChecking=no", 84 | "-o", 85 | "KbdInteractiveAuthentication=no", 86 | "-o", 87 | "ChallengeResponseAuthentication=no", 88 | "-o", 89 | "PasswordAuthentication=no", 90 | "localhost", 91 | "--", 92 | "true", 93 | ], 94 | stdin=subprocess.DEVNULL, 95 | timeout=10, 96 | ) 97 | except subprocess.TimeoutExpired: 98 | res = 1 99 | if res != 0: 100 | pytest.fail("Unable to ssh into localhost without password prompt.") 101 | 102 | mnt_dir = str(tmpdir.mkdir("mnt")) 103 | src_dir = str(tmpdir.mkdir("src")) 104 | 105 | cmdline = base_cmdline + [ 106 | pjoin(basename, "sshfs"), 107 | "-f", 108 | f"localhost:{src_dir}", 109 | mnt_dir, 110 | ] 111 | if debug: 112 | cmdline += ["-o", "sshfs_debug"] 113 | 114 | if sync_rd: 115 | cmdline += ["-o", "sync_readdir"] 116 | 117 | # SSHFS Cache 118 | if cache_timeout == 0: 119 | cmdline += ["-o", "dir_cache=no"] 120 | else: 121 | cmdline += ["-o", f"dcache_timeout={cache_timeout}", "-o", "dir_cache=yes"] 122 | 123 | # FUSE Cache 124 | cmdline += ["-o", "entry_timeout=0", "-o", "attr_timeout=0"] 125 | 126 | if multiconn: 127 | cmdline += ["-o", "max_conns=3"] 128 | 129 | new_env = dict(os.environ) # copy, don't modify 130 | 131 | # Abort on warnings from glib 132 | new_env["G_DEBUG"] = "fatal-warnings" 133 | 134 | mount_process = subprocess.Popen(cmdline, env=new_env) 135 | try: 136 | wait_for_mount(mount_process, mnt_dir) 137 | 138 | tst_statvfs(mnt_dir) 139 | tst_readdir(src_dir, mnt_dir) 140 | tst_open_read(src_dir, mnt_dir) 141 | tst_open_write(src_dir, mnt_dir) 142 | tst_append(src_dir, mnt_dir) 143 | tst_seek(src_dir, mnt_dir) 144 | tst_create(mnt_dir) 145 | tst_passthrough(src_dir, mnt_dir, cache_timeout) 146 | tst_mkdir(mnt_dir) 147 | tst_rmdir(src_dir, mnt_dir, cache_timeout) 148 | tst_unlink(src_dir, mnt_dir, cache_timeout) 149 | tst_symlink(mnt_dir) 150 | if os.getuid() == 0: 151 | tst_chown(mnt_dir) 152 | 153 | # SSHFS only supports one second resolution when setting 154 | # file timestamps. 155 | tst_utimens(mnt_dir, tol=1) 156 | tst_utimens_now(mnt_dir) 157 | 158 | tst_link(mnt_dir, cache_timeout) 159 | tst_truncate_path(mnt_dir) 160 | tst_truncate_fd(mnt_dir) 161 | tst_open_unlink(mnt_dir) 162 | except Exception as exc: 163 | cleanup(mount_process, mnt_dir) 164 | raise exc 165 | else: 166 | umount(mount_process, mnt_dir) 167 | 168 | 169 | def tst_unlink(src_dir, mnt_dir, cache_timeout): 170 | name = name_generator() 171 | fullname = mnt_dir + "/" + name 172 | with open(pjoin(src_dir, name), "wb") as fh: 173 | fh.write(b"hello") 174 | if cache_timeout: 175 | safe_sleep(cache_timeout + 1) 176 | assert name in os.listdir(mnt_dir) 177 | os.unlink(fullname) 178 | with pytest.raises(OSError) as exc_info: 179 | os.stat(fullname) 180 | assert exc_info.value.errno == errno.ENOENT 181 | assert name not in os.listdir(mnt_dir) 182 | assert name not in os.listdir(src_dir) 183 | 184 | 185 | def tst_mkdir(mnt_dir): 186 | dirname = name_generator() 187 | fullname = mnt_dir + "/" + dirname 188 | os.mkdir(fullname) 189 | fstat = os.stat(fullname) 190 | assert stat.S_ISDIR(fstat.st_mode) 191 | assert os.listdir(fullname) == [] 192 | assert fstat.st_nlink in (1, 2) 193 | assert dirname in os.listdir(mnt_dir) 194 | 195 | 196 | def tst_rmdir(src_dir, mnt_dir, cache_timeout): 197 | name = name_generator() 198 | fullname = mnt_dir + "/" + name 199 | os.mkdir(pjoin(src_dir, name)) 200 | if cache_timeout: 201 | safe_sleep(cache_timeout + 1) 202 | assert name in os.listdir(mnt_dir) 203 | os.rmdir(fullname) 204 | with pytest.raises(OSError) as exc_info: 205 | os.stat(fullname) 206 | assert exc_info.value.errno == errno.ENOENT 207 | assert name not in os.listdir(mnt_dir) 208 | assert name not in os.listdir(src_dir) 209 | 210 | 211 | def tst_symlink(mnt_dir): 212 | linkname = name_generator() 213 | fullname = mnt_dir + "/" + linkname 214 | os.symlink("/imaginary/dest", fullname) 215 | fstat = os.lstat(fullname) 216 | assert stat.S_ISLNK(fstat.st_mode) 217 | assert os.readlink(fullname) == "/imaginary/dest" 218 | assert fstat.st_nlink == 1 219 | assert linkname in os.listdir(mnt_dir) 220 | 221 | 222 | def tst_create(mnt_dir): 223 | name = name_generator() 224 | fullname = pjoin(mnt_dir, name) 225 | with pytest.raises(OSError) as exc_info: 226 | os.stat(fullname) 227 | assert exc_info.value.errno == errno.ENOENT 228 | assert name not in os.listdir(mnt_dir) 229 | 230 | fd = os.open(fullname, os.O_CREAT | os.O_RDWR) 231 | os.close(fd) 232 | 233 | assert name in os.listdir(mnt_dir) 234 | fstat = os.lstat(fullname) 235 | assert stat.S_ISREG(fstat.st_mode) 236 | assert fstat.st_nlink == 1 237 | assert fstat.st_size == 0 238 | 239 | 240 | def tst_chown(mnt_dir): 241 | filename = pjoin(mnt_dir, name_generator()) 242 | os.mkdir(filename) 243 | fstat = os.lstat(filename) 244 | uid = fstat.st_uid 245 | gid = fstat.st_gid 246 | 247 | uid_new = uid + 1 248 | os.chown(filename, uid_new, -1) 249 | fstat = os.lstat(filename) 250 | assert fstat.st_uid == uid_new 251 | assert fstat.st_gid == gid 252 | 253 | gid_new = gid + 1 254 | os.chown(filename, -1, gid_new) 255 | fstat = os.lstat(filename) 256 | assert fstat.st_uid == uid_new 257 | assert fstat.st_gid == gid_new 258 | 259 | 260 | def tst_open_read(src_dir, mnt_dir): 261 | name = name_generator() 262 | with open(pjoin(src_dir, name), "wb") as fh_out, open(TEST_FILE, "rb") as fh_in: 263 | shutil.copyfileobj(fh_in, fh_out) 264 | 265 | assert filecmp.cmp(pjoin(mnt_dir, name), TEST_FILE, False) 266 | 267 | 268 | def tst_open_write(src_dir, mnt_dir): 269 | name = name_generator() 270 | fd = os.open(pjoin(src_dir, name), os.O_CREAT | os.O_RDWR) 271 | os.close(fd) 272 | fullname = pjoin(mnt_dir, name) 273 | with open(fullname, "wb") as fh_out, open(TEST_FILE, "rb") as fh_in: 274 | shutil.copyfileobj(fh_in, fh_out) 275 | 276 | assert filecmp.cmp(fullname, TEST_FILE, False) 277 | 278 | 279 | def tst_append(src_dir, mnt_dir): 280 | name = name_generator() 281 | os_create(pjoin(src_dir, name)) 282 | fullname = pjoin(mnt_dir, name) 283 | with os_open(fullname, os.O_WRONLY) as fd: 284 | os.write(fd, b"foo\n") 285 | with os_open(fullname, os.O_WRONLY | os.O_APPEND) as fd: 286 | os.write(fd, b"bar\n") 287 | 288 | with open(fullname, "rb") as fh: 289 | assert fh.read() == b"foo\nbar\n" 290 | 291 | 292 | def tst_seek(src_dir, mnt_dir): 293 | name = name_generator() 294 | os_create(pjoin(src_dir, name)) 295 | fullname = pjoin(mnt_dir, name) 296 | with os_open(fullname, os.O_WRONLY) as fd: 297 | os.lseek(fd, 1, os.SEEK_SET) 298 | os.write(fd, b"foobar\n") 299 | with os_open(fullname, os.O_WRONLY) as fd: 300 | os.lseek(fd, 4, os.SEEK_SET) 301 | os.write(fd, b"com") 302 | 303 | with open(fullname, "rb") as fh: 304 | assert fh.read() == b"\0foocom\n" 305 | 306 | 307 | def tst_open_unlink(mnt_dir): 308 | name = pjoin(mnt_dir, name_generator()) 309 | data1 = b"foo" 310 | data2 = b"bar" 311 | fullname = pjoin(mnt_dir, name) 312 | with open(fullname, "wb+", buffering=0) as fh: 313 | fh.write(data1) 314 | os.unlink(fullname) 315 | with pytest.raises(OSError) as exc_info: 316 | os.stat(fullname) 317 | assert exc_info.value.errno == errno.ENOENT 318 | assert name not in os.listdir(mnt_dir) 319 | fh.write(data2) 320 | fh.seek(0) 321 | assert fh.read() == data1 + data2 322 | 323 | 324 | def tst_statvfs(mnt_dir): 325 | os.statvfs(mnt_dir) 326 | 327 | 328 | def tst_link(mnt_dir, cache_timeout): 329 | name1 = pjoin(mnt_dir, name_generator()) 330 | name2 = pjoin(mnt_dir, name_generator()) 331 | shutil.copyfile(TEST_FILE, name1) 332 | assert filecmp.cmp(name1, TEST_FILE, False) 333 | 334 | fstat1 = os.lstat(name1) 335 | assert fstat1.st_nlink == 1 336 | 337 | os.link(name1, name2) 338 | 339 | # The link operation changes st_ctime, and if we're unlucky 340 | # the kernel will keep the old value cached for name1, and 341 | # retrieve the new value for name2 (at least, this is the only 342 | # way I can explain the test failure). To avoid this problem, 343 | # we need to wait until the cached value has expired. 344 | if cache_timeout: 345 | safe_sleep(cache_timeout) 346 | 347 | fstat1 = os.lstat(name1) 348 | fstat2 = os.lstat(name2) 349 | for attr in ( 350 | "st_mode", 351 | "st_dev", 352 | "st_uid", 353 | "st_gid", 354 | "st_size", 355 | "st_atime", 356 | "st_mtime", 357 | "st_ctime", 358 | ): 359 | assert getattr(fstat1, attr) == getattr(fstat2, attr) 360 | assert os.path.basename(name2) in os.listdir(mnt_dir) 361 | assert filecmp.cmp(name1, name2, False) 362 | 363 | os.unlink(name2) 364 | 365 | assert os.path.basename(name2) not in os.listdir(mnt_dir) 366 | with pytest.raises(FileNotFoundError): 367 | os.lstat(name2) 368 | 369 | os.unlink(name1) 370 | 371 | 372 | def tst_readdir(src_dir, mnt_dir): 373 | newdir = name_generator() 374 | src_newdir = pjoin(src_dir, newdir) 375 | mnt_newdir = pjoin(mnt_dir, newdir) 376 | file_ = src_newdir + "/" + name_generator() 377 | subdir = src_newdir + "/" + name_generator() 378 | subfile = subdir + "/" + name_generator() 379 | 380 | os.mkdir(src_newdir) 381 | shutil.copyfile(TEST_FILE, file_) 382 | os.mkdir(subdir) 383 | shutil.copyfile(TEST_FILE, subfile) 384 | 385 | listdir_is = os.listdir(mnt_newdir) 386 | listdir_is.sort() 387 | listdir_should = [os.path.basename(file_), os.path.basename(subdir)] 388 | listdir_should.sort() 389 | assert listdir_is == listdir_should 390 | 391 | os.unlink(file_) 392 | os.unlink(subfile) 393 | os.rmdir(subdir) 394 | os.rmdir(src_newdir) 395 | 396 | 397 | def tst_truncate_path(mnt_dir): 398 | assert len(TEST_DATA) > 1024 399 | 400 | filename = pjoin(mnt_dir, name_generator()) 401 | with open(filename, "wb") as fh: 402 | fh.write(TEST_DATA) 403 | 404 | fstat = os.stat(filename) 405 | size = fstat.st_size 406 | assert size == len(TEST_DATA) 407 | 408 | # Add zeros at the end 409 | os.truncate(filename, size + 1024) 410 | assert os.stat(filename).st_size == size + 1024 411 | with open(filename, "rb") as fh: 412 | assert fh.read(size) == TEST_DATA 413 | assert fh.read(1025) == b"\0" * 1024 414 | 415 | # Truncate data 416 | os.truncate(filename, size - 1024) 417 | assert os.stat(filename).st_size == size - 1024 418 | with open(filename, "rb") as fh: 419 | assert fh.read(size) == TEST_DATA[: size - 1024] 420 | 421 | os.unlink(filename) 422 | 423 | 424 | def tst_truncate_fd(mnt_dir): 425 | assert len(TEST_DATA) > 1024 426 | with NamedTemporaryFile("w+b", 0, dir=mnt_dir) as fh: 427 | fd = fh.fileno() 428 | fh.write(TEST_DATA) 429 | fstat = os.fstat(fd) 430 | size = fstat.st_size 431 | assert size == len(TEST_DATA) 432 | 433 | # Add zeros at the end 434 | os.ftruncate(fd, size + 1024) 435 | assert os.fstat(fd).st_size == size + 1024 436 | fh.seek(0) 437 | assert fh.read(size) == TEST_DATA 438 | assert fh.read(1025) == b"\0" * 1024 439 | 440 | # Truncate data 441 | os.ftruncate(fd, size - 1024) 442 | assert os.fstat(fd).st_size == size - 1024 443 | fh.seek(0) 444 | assert fh.read(size) == TEST_DATA[: size - 1024] 445 | 446 | 447 | def tst_utimens(mnt_dir, tol=0): 448 | filename = pjoin(mnt_dir, name_generator()) 449 | os.mkdir(filename) 450 | fstat = os.lstat(filename) 451 | 452 | atime = fstat.st_atime + 42.28 453 | mtime = fstat.st_mtime - 42.23 454 | if sys.version_info < (3, 3): 455 | os.utime(filename, (atime, mtime)) 456 | else: 457 | atime_ns = fstat.st_atime_ns + int(42.28 * 1e9) 458 | mtime_ns = fstat.st_mtime_ns - int(42.23 * 1e9) 459 | os.utime(filename, None, ns=(atime_ns, mtime_ns)) 460 | 461 | fstat = os.lstat(filename) 462 | 463 | assert abs(fstat.st_atime - atime) < tol 464 | assert abs(fstat.st_mtime - mtime) < tol 465 | if sys.version_info >= (3, 3): 466 | assert abs(fstat.st_atime_ns - atime_ns) < tol * 1e9 467 | assert abs(fstat.st_mtime_ns - mtime_ns) < tol * 1e9 468 | 469 | 470 | def tst_utimens_now(mnt_dir): 471 | fullname = pjoin(mnt_dir, name_generator()) 472 | 473 | fd = os.open(fullname, os.O_CREAT | os.O_RDWR) 474 | os.close(fd) 475 | os.utime(fullname, None) 476 | 477 | fstat = os.lstat(fullname) 478 | # We should get now-timestamps 479 | assert fstat.st_atime != 0 480 | assert fstat.st_mtime != 0 481 | 482 | 483 | def tst_passthrough(src_dir, mnt_dir, cache_timeout): 484 | name = name_generator() 485 | src_name = pjoin(src_dir, name) 486 | mnt_name = pjoin(src_dir, name) 487 | assert name not in os.listdir(src_dir) 488 | assert name not in os.listdir(mnt_dir) 489 | with open(src_name, "w") as fh: 490 | fh.write("Hello, world") 491 | assert name in os.listdir(src_dir) 492 | if cache_timeout: 493 | safe_sleep(cache_timeout + 1) 494 | assert name in os.listdir(mnt_dir) 495 | assert os.stat(src_name) == os.stat(mnt_name) 496 | 497 | name = name_generator() 498 | src_name = pjoin(src_dir, name) 499 | mnt_name = pjoin(src_dir, name) 500 | assert name not in os.listdir(src_dir) 501 | assert name not in os.listdir(mnt_dir) 502 | with open(mnt_name, "w") as fh: 503 | fh.write("Hello, world") 504 | assert name in os.listdir(src_dir) 505 | if cache_timeout: 506 | safe_sleep(cache_timeout + 1) 507 | assert name in os.listdir(mnt_dir) 508 | assert os.stat(src_name) == os.stat(mnt_name) 509 | -------------------------------------------------------------------------------- /test/travis-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Disable leak checking for now, there are some issues (or false positives) 6 | # that we still need to fix 7 | export ASAN_OPTIONS="detect_leaks=0" 8 | 9 | export LSAN_OPTIONS="suppressions=${PWD}/test/lsan_suppress.txt" 10 | export CC 11 | 12 | TEST_CMD="python3 -m pytest --maxfail=99 test/" 13 | 14 | # Standard build with Valgrind 15 | for CC in gcc clang; do 16 | ( 17 | mkdir "build-${CC}"; cd "build-${CC}" 18 | if [ "${CC}" == 'gcc-6' ]; then 19 | build_opts='-D b_lundef=false' 20 | else 21 | build_opts='' 22 | fi 23 | # shellcheck disable=SC2086 24 | meson -D werror=true ${build_opts} ../ 25 | ninja 26 | 27 | TEST_WITH_VALGRIND=true ${TEST_CMD} 28 | ) 29 | done 30 | (cd "build-${CC}"; sudo ninja install) 31 | 32 | # Sanitized build 33 | CC=clang 34 | for san in undefined address; do 35 | ( 36 | mkdir "build-${san}" 37 | cd "build-${san}" 38 | # b_lundef=false is required to work around clang 39 | # bug, cf. https://groups.google.com/forum/#!topic/mesonbuild/tgEdAXIIdC4 40 | meson -D b_sanitize=${san} -D b_lundef=false -D werror=true .. 41 | ninja 42 | ${TEST_CMD} 43 | sudo ninja install 44 | ) 45 | done 46 | -------------------------------------------------------------------------------- /test/travis-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # Install fuse 6 | wget https://github.com/libfuse/libfuse/archive/master.zip 7 | unzip master.zip 8 | cd libfuse-master 9 | mkdir build 10 | cd build 11 | meson .. 12 | ninja 13 | sudo ninja install 14 | test -e /usr/local/lib/pkgconfig || sudo mkdir /usr/local/lib/pkgconfig 15 | sudo mv /usr/local/lib/*/pkgconfig/* /usr/local/lib/pkgconfig/ 16 | printf '%s\n' /usr/local/lib/*-linux-gnu | sudo tee /etc/ld.so.conf.d/usrlocal.conf 17 | sudo ldconfig 18 | 19 | # Setup ssh 20 | ssh-keygen -b 1024 -t rsa -f ~/.ssh/id_rsa -P '' 21 | cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys 22 | chmod 600 ~/.ssh/authorized_keys 23 | ssh -o "StrictHostKeyChecking=no" localhost echo "SSH connection succeeded" 24 | -------------------------------------------------------------------------------- /test/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import subprocess 3 | import pytest 4 | import os 5 | import stat 6 | import time 7 | from os.path import join as pjoin 8 | from contextlib import contextmanager 9 | 10 | basename = pjoin(os.path.dirname(__file__), "..") 11 | 12 | 13 | def os_create(name): 14 | os.close(os.open(name, os.O_CREAT | os.O_RDWR)) 15 | 16 | 17 | @contextmanager 18 | def os_open(name, flags): 19 | fd = os.open(name, flags) 20 | try: 21 | yield fd 22 | finally: 23 | os.close(fd) 24 | 25 | 26 | def wait_for_mount(mount_process, mnt_dir, test_fn=os.path.ismount): 27 | elapsed = 0 28 | while elapsed < 30: 29 | if test_fn(mnt_dir): 30 | return True 31 | if mount_process.poll() is not None: 32 | pytest.fail("file system process terminated prematurely") 33 | time.sleep(0.1) 34 | elapsed += 0.1 35 | pytest.fail("mountpoint failed to come up") 36 | 37 | 38 | def cleanup(mount_process, mnt_dir): 39 | subprocess.call( 40 | ["fusermount", "-z", "-u", mnt_dir], 41 | stdout=subprocess.DEVNULL, 42 | stderr=subprocess.STDOUT, 43 | ) 44 | mount_process.terminate() 45 | try: 46 | mount_process.wait(1) 47 | except subprocess.TimeoutExpired: 48 | mount_process.kill() 49 | 50 | 51 | def umount(mount_process, mnt_dir): 52 | subprocess.check_call(["fusermount3", "-z", "-u", mnt_dir]) 53 | assert not os.path.ismount(mnt_dir) 54 | 55 | # Give mount process a little while to terminate. Popen.wait(timeout) 56 | # was only added in 3.3... 57 | elapsed = 0 58 | while elapsed < 30: 59 | code = mount_process.poll() 60 | if code is not None: 61 | if code == 0: 62 | return 63 | pytest.fail(f"file system process terminated with code {code}") 64 | time.sleep(0.1) 65 | elapsed += 0.1 66 | pytest.fail("mount process did not terminate") 67 | 68 | 69 | def safe_sleep(secs): 70 | """Like time.sleep(), but sleep for at least *secs* 71 | 72 | `time.sleep` may sleep less than the given period if a signal is 73 | received. This function ensures that we sleep for at least the 74 | desired time. 75 | """ 76 | 77 | now = time.time() 78 | end = now + secs 79 | while now < end: 80 | time.sleep(end - now) 81 | now = time.time() 82 | 83 | 84 | def fuse_test_marker(): 85 | """Return a pytest.marker that indicates FUSE availability 86 | 87 | If system/user/environment does not support FUSE, return 88 | a `pytest.mark.skip` object with more details. If FUSE is 89 | supported, return `pytest.mark.uses_fuse()`. 90 | """ 91 | 92 | def skip(reason: str): 93 | return pytest.mark.skip(reason=reason) 94 | 95 | with subprocess.Popen( 96 | ["which", "fusermount"], stdout=subprocess.PIPE, universal_newlines=True 97 | ) as which: 98 | fusermount_path = which.communicate()[0].strip() 99 | 100 | if not fusermount_path or which.returncode != 0: 101 | return skip("Can't find fusermount executable") 102 | 103 | if not os.path.exists("/dev/fuse"): 104 | return skip("FUSE kernel module does not seem to be loaded") 105 | 106 | if os.getuid() == 0: 107 | return pytest.mark.uses_fuse() 108 | 109 | mode = os.stat(fusermount_path).st_mode 110 | if mode & stat.S_ISUID == 0: 111 | return skip("fusermount executable not setuid, and we are not root.") 112 | 113 | try: 114 | fd = os.open("/dev/fuse", os.O_RDWR) 115 | except OSError as exc: 116 | return skip(f"Unable to open /dev/fuse: {exc.strerror}") 117 | else: 118 | os.close(fd) 119 | 120 | return pytest.mark.uses_fuse() 121 | 122 | 123 | # Use valgrind if requested 124 | if os.environ.get("TEST_WITH_VALGRIND", "no").lower().strip() not in ( 125 | "no", 126 | "false", 127 | "0", 128 | ): 129 | base_cmdline = ["valgrind", "-q", "--"] 130 | else: 131 | base_cmdline = [] 132 | -------------------------------------------------------------------------------- /test/wrong_command.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void) { 4 | fprintf(stderr, "\x1B[31m\e[1m" 5 | "This is not the command you are looking for.\n" 6 | "You probably want to run 'python3 -m pytest test/' instead" 7 | "\e[0m\n"); 8 | return 1; 9 | } 10 | -------------------------------------------------------------------------------- /utils/install_helper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Don't call this script. It is used internally by the Meson 4 | # build system. Thank you for your cooperation. 5 | # 6 | 7 | set -e 8 | 9 | bindir="$2" 10 | sbindir="$1" 11 | prefix="${MESON_INSTALL_DESTDIR_PREFIX}" 12 | 13 | mkdir -p "${prefix}/${sbindir}" 14 | 15 | ln -svf --relative "${prefix}/${bindir}/sshfs" \ 16 | "${prefix}/${sbindir}/mount.sshfs" 17 | 18 | ln -svf --relative "${prefix}/${bindir}/sshfs" \ 19 | "${prefix}/${sbindir}/mount.fuse.sshfs" 20 | --------------------------------------------------------------------------------