[
  {
    "path": ".appveyor.yml",
    "content": "version: '{build}'\n\ninstall:\n\n# install WinFsp\n- appveyor DownloadFile https://github.com/billziss-gh/winfsp/releases/download/v1.4B2/winfsp-1.4.18211.msi\n- for %%f in (\"winfsp-*.msi\") do start /wait msiexec /i %%f /qn INSTALLLEVEL=1000\n\n# install FUSE for Cygwin (64-bit and 32-bit)\n- C:\\cygwin64\\bin\\env.exe -i PATH=/bin bash \"%ProgramFiles(x86)%\\WinFsp\\opt\\cygfuse\\install.sh\"\n- C:\\cygwin\\bin\\env.exe -i PATH=/bin bash \"%ProgramFiles(x86)%\\WinFsp\\opt\\cygfuse\\install.sh\"\n\n# install additional Cygwin packages (64-bit and 32-bit)\n- 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\n- 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\n\nbuild_script:\n- C:\\cygwin64\\bin\\env.exe -i PATH=/bin bash test\\appveyor-build.sh\n- C:\\cygwin\\bin\\env.exe -i PATH=/bin bash test\\appveyor-build.sh\n"
  },
  {
    "path": ".dir-locals.el",
    "content": "((python-mode . ((indent-tabs-mode . nil)))\n (autoconf-mode . ((indent-tabs-mode . t)))\n (c-mode . ((c-file-style . \"stroustrup\")\n\t    (indent-tabs-mode . t)\n\t    (tab-width . 8)\n\t    (c-basic-offset . 8)\n\t    (c-file-offsets .\n\t\t\t    ((block-close . 0)\n\t\t\t     (brace-list-close . 0)\n\t\t\t     (brace-list-entry . 0)\n\t\t\t     (brace-list-intro . +)\n\t\t\t     (case-label . 0)\n\t\t\t     (class-close . 0)\n\t\t\t     (defun-block-intro . +)\n\t\t\t     (defun-close . 0)\n\t\t\t     (defun-open . 0)\n\t\t\t     (else-clause . 0)\n\t\t\t     (inclass . +)\n\t\t\t     (label . 0)\n\t\t\t     (statement . 0)\n\t\t\t     (statement-block-intro . +)\n\t\t\t     (statement-case-intro . +)\n\t\t\t     (statement-cont . +)\n\t\t\t     (substatement . +)\n\t\t\t     (topmost-intro . 0))))))\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "d54c7ecbd618afb4df524e0d96dec7fe7cc2935d\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue-report.md",
    "content": "---\nname: Issue report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\nPLEASE READ BEFORE REPORTING AN ISSUE\n\nSSHFS 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.\n\nTo 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.\n\nPlease 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,\nand at the moment there simply aren't any volunteers.\n"
  },
  {
    "path": ".github/workflows/build-ubuntu.yml",
    "content": "name: build and test\non:\n  push:\n\n  pull_request:\n\n  workflow_dispatch: # this is a nice option that will enable a button w/ inputs\n    inputs:\n      git-ref:\n        description: Git Ref (Optional)    \n        required: false\njobs:\n  build-and-test:\n    name: Build and test\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - uses: actions/setup-python@v4\n\n      - name: Install build dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install valgrind gcc ninja-build meson libglib2.0-dev libfuse3-dev\n\n      - name: Install meson\n        run: pip3 install meson pytest\n\n      - name: build\n        run: |\n          mkdir build; cd build\n          meson ..\n          ninja\n\n      # cd does not persist across steps\n      - name: upload build artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: sshfs\n          path: build/sshfs\n\n      - name: make ssh into localhost without prompt possible for tests\n        run: |\n          ssh-keygen -b 2048 -t rsa  -f ~/.ssh/id_rsa -q -N \"\"\n          cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys\n\n      - name: run tests\n        run: |\n          cd build\n          python3 -m pytest test/\n"
  },
  {
    "path": ".gitignore",
    "content": "#\n# NOTE! Don't add files that are generated in specific\n# subdirectories here. Add them in the \".gitignore\" file\n# in that subdirectory instead.\n#\n# NOTE! Please use 'git ls-files -i --exclude-standard'\n# command after changing this file, to see if there are\n# any tracked files which get ignored after the change.\n*.o\n*.lo\n*.la\n*.gz\n\\#*#\n*.orig\n*~\nMakefile.in\nMakefile\n*.m4\nstamp-h*\nconfig.*\nsshfs.1\n/sshfs\n/ltmain.sh\n/configure\n/install-sh\n/mkinstalldirs\n/missing\n/*.cache\n/depcomp\n/compile\n/libtool\n/INSTALL\n/.pc\n/patches\n/m4\n.deps/\n/build\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n- repo: https://github.com/pre-commit/pre-commit-hooks\n  rev: v4.0.1\n  hooks:\n    - id: trailing-whitespace\n    - id: end-of-file-fixer\n    - id: check-yaml\n    - id: check-added-large-files\n- repo: https://github.com/jumanjihouse/pre-commit-hooks\n  rev: 2.1.5\n  hooks:\n    - id: shellcheck\n"
  },
  {
    "path": ".travis.yml",
    "content": "dist: focal\n\nlanguage: c\n\ncache:\n  - pip\n\naddons:\n  apt:\n    packages:\n    - shellcheck\n    - valgrind\n    - gcc\n    - clang\n    - python-docutils\n    - python3-pip\n    - python3-setuptools\n    - ninja-build\n    - meson\n    - python3-pytest\n    - libglib2.0-dev\n\ninstall: test/travis-install.sh\n\njobs:\n  include:\n    - name: Lint\n      script: ./test/lint.sh\n      install: skip\n    - name: Build + Test\n      script: test/travis-build.sh\n"
  },
  {
    "path": "AUTHORS",
    "content": "Current Maintainer\n------------------\n\nNone.\n\n\nPast Maintainers\n----------------\n\nNikolaus Rath <Nikolaus@rath.org> (until 05/2022)\nMiklos Szeredi <miklos@szeredi.hu> (until 12/2015)\n\n\nContributors (autogenerated list)\n---------------------------------\n\na1346054 <36859588+a1346054@users.noreply.github.com>\nAlan Jenkins <alan.christopher.jenkins@gmail.com>\nAlexander Neumann <alexander@bumpern.de>\nAnatol Pomozov <anatol.pomozov@gmail.com>\nAndrew Stone <a@stne.dev>\nAntonio Rojas <arojas@archlinux.org>\nBenjamin Fleischer <fleiben@gmail.com>\nBerserker <berserker.troll@yandex.com>\nBill Zissimopoulos <billziss@navimatics.com>\nbjoe2k4 <bjoe2k4@users.noreply.github.com>\nBrandon Carter <b-carter@users.noreply.github.com>\nCam Cope <github@camcope.me>\nChris Wolfe <cwolfe@chromium.org>\nClayton G. Hobbs <clay@lakeserv.net>\nDaniel Lublin <daniel@lublin.se>\nDominique Martinet <asmadeus@codewreck.org>\nDrDaveD <2129743+DrDaveD@users.noreply.github.com>\nFabrice Fontaine <fontaine.fabrice@gmail.com>\ngala <gala132@users.noreply.github.com>\nGalen Getsov <4815620+ggetsov@users.noreply.github.com>\nGeorge Vlahavas <vlahavas@gmail.com>\nG.raud Meyer <graud@gmx.com>\nharrim4n <git@harrim4n.com>\nJakub Jelen <jjelen@redhat.com>\njeg139 <54814784+jeg139@users.noreply.github.com>\nJosh Triplett <josh@joshtriplett.org>\nJulio Merino <jmmv@google.com>\nJulio Merino <jmmv@meroh.net>\nJunichi Uekawa <dancerj@gmail.com>\nJunichi Uekawa <dancer@netfort.gr.jp>\nkalvdans <github@kalvdans.no-ip.org>\nKim Brose <kim.brose@rwth-aachen.de>\nMatthew Berginski <matthew.berginski@gmail.com>\nMichael Forney <mforney@mforney.org>\nMike Kelly <mike@pair.com>\nMike Salvatore <mike.s.salvatore@gmail.com>\nMiklos Szeredi <miklos@szeredi.hu>\nMiklos Szeredi <mszeredi@suse.cz>\nmssalvatore <mike.s.salvatore@gmail.com>\nNikolaus Rath <Nikolaus@rath.org>\nPercy Jahn <email@percyjahn.de>\nPeter Belm <peterbelm@gmail.com>\nPeter Wienemann <peter.wienemann@uni-bonn.de>\nQais Patankar <qaisjp@gmail.com>\nQuentin Rameau <quinq@fifth.space>\nReid Wagner <wagnerreid@gmail.com>\nRian Hunter <rian@alum.mit.edu>\nRian Hunter <rianhunter@users.noreply.github.com>\nSamuel Murray <samuel.murray@outlook.com>\nS. D. Cloudt <s.d.cloudt@student.tue.nl>\nSimon Arlott <70171+nomis@users.noreply.github.com>\nsmheidrich <smheidrich@weltenfunktion.de>\nsunwire <50745572+sunwire@users.noreply.github.com>\nTim Harder <radhermit@gmail.com>\nTimo Savola <timo.savola@iki.fi>\ntpoindessous <thomas@poindessous.com>\nViktor Szakats <vszakats@users.noreply.github.com>\nZoltan Kuscsik <zoltan.kuscsik@linaro.org>\n"
  },
  {
    "path": "COPYING",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "ChangeLog.rst",
    "content": "Release 3.7.5 (2025-11-11)\n--------------------------\n\n* New maintainers added\n* Main documentation updates to using Markdown\n* Updated Windows and macOS support\n* Added IPv6 support in -o directport\n* Added -o vsock option\n* Minor bugfixes\n\nRelease 3.7.3 (2022-05-26)\n--------------------------\n\n* Minor bugfixes.\n\n* This is the last release from the current maintainer. SSHFS is now no longer maintained\n  or developed. Github issue tracking and pull requests have therefore been disabled. The\n  mailing list (see below) is still available for use.\n\n  If you would like to take over this project, you are welcome to do so. Please fork it\n  and develop the fork for a while. Once there has been 6 months of reasonable activity,\n  please contact Nikolaus@rath.org and I'll be happy to give you ownership of this\n  repository or replace with a pointer to the fork.\n\n\nRelease 3.7.2 (2021-06-08)\n--------------------------\n\n* Added a secondary check so if a mkdir request fails with EPERM an access request will be\n  tried - returning EEXIST if the access was successful.\n\tFixes: https://github.com/libfuse/sshfs/issues/243\n\n\nRelease 3.7.1 (2020-11-09)\n--------------------------\n\n* Minor bugfixes.\n\n\nRelease 3.7.0 (2020-01-03)\n--------------------------\n\n* New max_conns option enables the use of multiple connections to improve responsiveness\n  during large file transfers. Thanks to Timo Savola for doing most of the implementation\n  work, and thanks to CEA.fr for sponsoring remaining bugfixes and cleanups!\n\n* The `buflimit` workaround is now disabled by default. The corresponding bug in OpenSSH\n  has been fixed in 2007\n  (cf. https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=365541#37), so this shouldn't be\n  needed anymore. If you depend on this workaround, please let the SSHFS maintainers know,\n  otherwise support for the workaround will be removed completely in a future version.\n\n\nRelease 3.6.0 (2019-11-03)\n--------------------------\n\n* Added \"-o direct_io\" option.\n  This option disables the use of page cache in kernel.\n  This is useful for example if the file size is not known before reading it.\n  For example if you mount /proc dir from a remote host without the direct_io\n  option, the read always will return zero bytes instead of actual data.\n* Added --verbose option.\n* Fixed a number of compiler warnings.\n* Improved performance under OS X.\n\n\nRelease 3.5.2 (2019-04-13)\n--------------------------\n\n* Fixed \"-o idmap=user\" to map both UID and GID on all OSs.\n* Fixed improper handling of sequential spaces spaces in \"ssh_command\" option\n\nRelease 3.5.1 (2018-12-22)\n--------------------------\n\n* Documentation updates\n* Build system updates\n* Added \"BindInterface\" as valid \"-o\" option.\n\nRelease 3.5.0 (2018-08-28)\n--------------------------\n\n* Fixed error code returned by rename(), allowing proper fallback.\n* Port to Cygwin.\n\nRelease 3.4.0 (2018-06-29)\n--------------------------\n\n* Make utimens(NULL) result in timestamp \"now\" -- no more touched files\n  dated 1970-01-01\n* New `createmode` workaround.\n* Fix `fstat` workaround regression.\n\nRelease 3.3.2 (2018-04-29)\n--------------------------\n\n* New `renamexdev` workaround.\n\nRelease 3.3.1 (2017-10-25)\n--------------------------\n\n* Manpage is now installed in correct directory.\n* SSHFS now supports (or rather: ignores) some options that it may\n  receive as result of being mounted from ``/etc/mtab``. This includes\n  things like ``user``, ``netdev``, or ``auto``.\n\nSSHFS 3.3.0 (2017-09-20)\n------------------------\n\n* Dropped support for writeback caching (and, as a consequence,\n  \"unreliable append\" operation). As of kernel 4.14, the FUSE module's\n  writeback implementation is not compatible with network filesystems\n  and there are no imminent plans to change that.\n* Add support for mounting from /etc/fstab\n* Dropped support for building with autotools.\n* Added missing options to man page.\n\nRelease 3.2.0 (2017-08-06)\n--------------------------\n\n* Re-enabled writeback cache.\n* SSHFS now supports O_APPEND.\n\nRelease 3.1.0 (2017-08-04)\n--------------------------\n\n* Temporarily disabled the writeback cache feature, since there\n  have been reports of dataloss when appending to files when\n  writeback caching is enabled.\n\n* Fixed a crash due to a race condition when listing\n  directory contents.\n\n* For improved backwards compatibility, SSHFS now also silently\n  accepts the old ``-o cache_*`` options.\n\nRelease 3.0.0 (2017-07-08)\n--------------------------\n\n* sshfs now requires libfuse 3.1.0 or newer.\n* When supported by the kernel, sshfs now uses writeback caching.\n* The `cache` option has been renamed to `dir_cache` for clarity.\n* Added unit tests\n* --debug now behaves like -o debug_sshfs, i.e. it enables sshfs\n  debugging messages rather than libfuse debugging messages.\n* Documented limited hardlink support.\n* Added support for building with Meson.\n* Added support for more SSH options.\n* Dropped support for the *nodelay* workaround - the last OpenSSH\n  version for which this was useful was released in 2006.\n* Dropped support for the *nodelaysrv* workaround. The same effect\n  (enabling NODELAY on the server side *and* enabling X11 forwarding)\n  can be achieved by explicitly passing `-o ForwardX11`\n* Removed support for `-o workaround=all`. Workarounds should always\n  enabled explicitly and only when needed. There is no point in always\n  enabling a potentially changing set of workarounds.\n\nRelease 2.9 (2017-04-17)\n------------------------\n\n* Improved support for Cygwin.\n* Various small bugfixes.\n\nRelease 2.8 (2016-06-22)\n------------------------\n\n* Added support for the \"fsync\" extension.\n* Fixed a build problem with bitbake\n\nRelease 2.7 (2016-03-01)\n------------------------\n\n* Integrated osxfuse's copy of sshfs, which means that sshfs now works\n  on OS X out of the box.\n* Added -o cache_max_size=N option to let users tune the maximum size of\n  the cache in number of entries.\n* Added -o cache_clean_interval=N and -o cache_min_clean_interval=N\n  options to let users tune the cleaning behavior of the cache.\n\nRelease 2.6 (2015-01-28)\n------------------------\n\n* New maintainer (Nikolaus Rath <Nikolaus@rath.org>)\n\nRelease 2.5 (2014-01-14)\n------------------------\n\n* Some performance improvements for large directories.\n* New `disable_hardlink` option.\n* Various small bugfixes.\n\nRelease 2.4 (2012-03-08)\n------------------------\n\n* New `slave` option.\n* New `idmap`, `uidmap` and `gidmap` options.\n* Various small bugfixes.\n\nRelease 2.3 (2011-07-01)\n------------------------\n\n* Support hard link creation if server is OpenSSH 5.7 or later\n* Small improvements and bug fixes\n* Check mount point and options before connecting to ssh server\n* New 'delay_connect' option\n\nRelease 2.2 (2008-10-20)\n------------------------\n\n* Handle numerical IPv6 addresses enclosed in square brackets\n* Handle commas in usernames\n\nRelease 2.1 (2008-07-11)\n------------------------\n\n* Small improvements and bug fixes\n\nRelease 2.0 (2008-04-23)\n------------------------\n\n* Support password authentication with pam_mount\n\n* Support atomic renames if server is OpenSSH 4.9 or later\n\n* Support getting disk usage if server is OpenSSH 5.1 or later\n\n* Small enhancements and bug fixes\n\nWhat is new in 1.9\n------------------\n\n* Fix a serious bug, that could result in sshfs hanging, crashing, or\n  reporting out-of-memory\n\nWhat is new in 1.8\n------------------\n\n* Bug fixes\n\nWhat is new in 1.7\n------------------\n\n* Tolerate servers which print a banner on login\n\n* Small improvements\n\nWhat is new in 1.6\n------------------\n\n* Workaround for missing truncate operation on old sftp servers\n\n* Bug fixes\n\nWhat is new in 1.5\n------------------\n\n* Improvements to read performance.  Now both read and write\n  throughput should be very close to 'scp'\n\n* If used with FUSE 2.6.0 or later, then perform better data caching.\n  This should show dramatic speed improvements when a file is opened\n  more than once\n\n* Bug fixes\n\nWhat is new in 1.4\n------------------\n\n* Updated to version 25 of libfuse API\n\n* This means that the 'cp' of readonly file to sshfs bug is finally\n  solved (as long as using libfuse 2.5.0 or later *and* Linux 2.6.15\n  or later)\n\n* Sshfs now works on FreeBSD\n\n* Added option to \"transform\" absolute symbolic links\n\nWhat is new in 1.3\n------------------\n\n* Add workaround for failure to rename to an existing file\n\n* Simple user ID mapping\n\n* Estimate disk usage of files based on size\n\n* Report \"infinite\" disk space\n\n* Bug fixes\n\nWhat is new in 1.2\n------------------\n\n* Better compatibility with different sftp servers\n\n* Automatic reconnect (optional)\n\nWhat is new in 1.1\n------------------\n\n* Performance improvements:\n\n   - directory content caching\n\n   - symlink caching\n\n   - asynchronous writeback\n\n   - readahead\n\n* Fixed '-p' option\n\nWhat is new in 1.0\n------------------\n\n* Initial release\n"
  },
  {
    "path": "README.md",
    "content": "\n# SSHFS\n\n\n## About\n\nSSHFS allows you to mount a remote filesystem using SFTP. Most SSH\nservers support and enable this SFTP access by default, so SSHFS is\nvery simple to use - there's nothing to do on the server-side.\n\n\n## Development Status\n\n\nSSHFS is shipped by all major Linux distributions and has been in\nproduction use across a wide range of systems for many years. However,\nat present SSHFS does not have any active, regular contributors, and\nthere are a number of known issues (see the [bugtracker](https://github.com/libfuse/sshfs/issues)).  \nThe current maintainer continues to apply pull requests and makes regular\nreleases, but unfortunately has no capacity to do any development\nbeyond addressing high-impact issues. When reporting bugs, please\nunderstand that unless you are including a pull request or are\nreporting a critical issue, you will probably not get a response.\n\n\n## How to use\n\n\nOnce sshfs is installed (see next section) running it is very simple:\n\n```\nsshfs [user@]hostname:[directory] mountpoint\n```\n\nIt is recommended to run SSHFS as regular user (not as root).  For\nthis to work the mountpoint must be owned by the user.  If username is\nomitted SSHFS will use the local username. If the directory is\nomitted, SSHFS will mount the (remote) home directory.  If you need to\nenter a password sshfs will ask for it (actually it just runs ssh\nwhich asks for the password if needed).\n\nAlso many ssh options can be specified (see the manual pages for\n*sftp(1)* and *ssh_config(5)*), including the remote port number\n(`-oport=PORT`)\n\nTo unmount the filesystem:\n\n```\nfusermount -u mountpoint\n```\n\nOn BSD and macOS, to unmount the filesystem:\n\n```\numount mountpoint\n```\n\n### Bypassing SSH\n\n#### Using directport\n\nUsing direct connections to sftp-server to bypass SSH for performance is also possible. To do this, start a network service using sftp-server (part of OpenSSH) on a server, then connect directly using `-o directport=PORT` option.\n\nOn server (listen on port 1234 using socat):\n\n`socat tcp-listen:1234,reuseaddr,fork  exec:/usr/lib/openssh/sftp-server`\n\nOn client:\n\n`sshfs -o directport=1234 127.0.0.1:/tmp /tmp/mnt`\n\nNote that this is insecure as connection will happen without encryption. Only use this on localhost or trusted networks. This option is sometimes used by other projects to mount folders inside VMs.\n\nIPv6 is also possible:\n\n`socat tcp6-listen:1234,reuseaddr,fork exec:/usr/lib/openssh/sftp-server`\n\n`sshfs -o directport=1234 [::1]:/tmp /tmp/mnt`\n\n#### Using vsock\n\nSimilarly to above, Linux [vsock](https://man7.org/linux/man-pages/man7/vsock.7.html) can be used to connect directly to sockets within VMs using `-o vsock=CID:PORT`.\n\n```\n# on the host\nsocat VSOCK-LISTEN:12345 EXEC:\"/usr/lib/openssh/sftp-server\",nofork\n# on the clientside\nsshfs -o vsock=2:12345 unused_host: ./tmp\n```\n\n## Installation\n\n\nFirst, download the latest SSHFS release from\nhttps://github.com/libfuse/sshfs/releases. You also need [libfuse](http://github.com/libfuse/libfuse) 3.1.0 or newer (or a\nsimilar library that provides a libfuse3 compatible interface for your operating\nsystem). Finally, you need the [Glib](https://developer.gnome.org/glib/stable/) library with development headers (which should be\navailable from your operating system's package manager).\n\nTo build and install, we recommend to use [Meson](http://mesonbuild.com/) (version 0.38 or\nnewer) and [Ninja](https://ninja-build.org/).  After extracting the sshfs tarball, create a\n(temporary) build directory and run Meson:\n\n```\n$ mkdir build; cd build\n$ meson ..\n```\n\nNormally, the default build options will work fine. If you\nnevertheless want to adjust them, you can do so with the *mesonconf*\ncommand:\n\n```\n$ mesonconf                  # list options\n$ mesonconf -D strip=true    # set an option\n```\n\nTo build, test and install SSHFS, you then use Ninja (running the\ntests requires the [py.test](http://www.pytest.org/) Python module):\n\n```\n$ ninja\n$ python3 -m pytest test/    # optional, but recommended\n$ sudo ninja install\n```\n\n## Getting Help\n\n\nIf you need help, please ask on the <fuse-sshfs@lists.sourceforge.net>\nmailing list (subscribe at\nhttps://lists.sourceforge.net/lists/listinfo/fuse-sshfs).\n\nPlease report any bugs on the GitHub issue tracker at\nhttps://github.com/libfuse/sshfs/issues.\n\n## Packaging Status\n\n\n<a href=\"https://repology.org/project/fusefs:sshfs/versions\">\n    <img src=\"https://repology.org/badge/vertical-allrepos/fusefs:sshfs.svg\" alt=\"Packaging status\" >\n</a>\n"
  },
  {
    "path": "cache.c",
    "content": "/*\n  Caching file system proxy\n  Copyright (C) 2004  Miklos Szeredi <miklos@szeredi.hu>\n\n  This program can be distributed under the terms of the GNU GPL.\n  See the file COPYING.\n*/\n\n#include \"cache.h\"\n#include <stdio.h>\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n#include <glib.h>\n#include <pthread.h>\n#include <sys/stat.h>\n\n#define DEFAULT_CACHE_TIMEOUT_SECS 20\n#define DEFAULT_MAX_CACHE_SIZE 10000\n#define DEFAULT_CACHE_CLEAN_INTERVAL_SECS 60\n#define DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS 5\n\nstruct cache {\n\tint on;\n\tunsigned int stat_timeout_secs;\n\tunsigned int dir_timeout_secs;\n\tunsigned int link_timeout_secs;\n\tunsigned int max_size;\n\tunsigned int clean_interval_secs;\n\tunsigned int min_clean_interval_secs;\n\tstruct fuse_operations *next_oper;\n\tGHashTable *table;\n\tpthread_mutex_t lock;\n\ttime_t last_cleaned;\n\tuint64_t write_ctr;\n};\n\nstatic struct cache cache;\n\nstruct node {\n\tstruct stat stat;\n\ttime_t stat_valid;\n\tGPtrArray *dir;\n\ttime_t dir_valid;\n\tchar *link;\n\ttime_t link_valid;\n\ttime_t valid;\n};\n\nstruct readdir_handle {\n\tconst char *path;\n\tvoid *buf;\n\tfuse_fill_dir_t filler;\n\tGPtrArray *dir;\n\tuint64_t wrctr;\n};\n\nstruct file_handle {\n\t/* Did we send an open request to the underlying fs? */\n\tint is_open;\n\n\t/* If so, this will hold its handle */\n\tunsigned long fs_fh;\n};\n\nstruct cache_dirent {\n\tchar *name;\n\tstruct stat stat;\n};\n\nstatic void free_node(gpointer node_)\n{\n\tstruct node *node = (struct node *) node_;\n\tif (node->dir != NULL) {\n\t\tg_ptr_array_free(node->dir, TRUE);\n  }\n\tg_free(node);\n}\n\nstatic void free_cache_dirent(gpointer data) {\n\tstruct cache_dirent *cache_dirent = (struct cache_dirent *) data;\n\tif (cache_dirent != NULL) {\n\t\tg_free(cache_dirent->name);\n\t\tg_free(cache_dirent);\n\t}\n}\n\nstatic int cache_clean_entry(void *key_, struct node *node, time_t *now)\n{\n\t(void) key_;\n\tif (*now > node->valid)\n\t\treturn TRUE;\n\telse\n\t\treturn FALSE;\n}\n\nstatic void cache_clean(void)\n{\n\ttime_t now = time(NULL);\n\tif (now > cache.last_cleaned + cache.min_clean_interval_secs &&\n\t    (g_hash_table_size(cache.table) > cache.max_size ||\n\t     now > cache.last_cleaned + cache.clean_interval_secs)) {\n\t\tg_hash_table_foreach_remove(cache.table,\n\t\t\t\t\t    (GHRFunc) cache_clean_entry, &now);\n\t\tcache.last_cleaned = now;\n\t}\n}\n\nstatic struct node *cache_lookup(const char *path)\n{\n\treturn (struct node *) g_hash_table_lookup(cache.table, path);\n}\n\nstatic void cache_purge(const char *path)\n{\n\tg_hash_table_remove(cache.table, path);\n}\n\nstatic void cache_purge_parent(const char *path)\n{\n\tconst char *s = strrchr(path, '/');\n\tif (s) {\n\t\tif (s == path)\n\t\t\tg_hash_table_remove(cache.table, \"/\");\n\t\telse {\n\t\t\tchar *parent = g_strndup(path, s - path);\n\t\t\tcache_purge(parent);\n\t\t\tg_free(parent);\n\t\t}\n\t}\n}\n\nvoid cache_invalidate(const char *path)\n{\n\tpthread_mutex_lock(&cache.lock);\n\tcache_purge(path);\n\tpthread_mutex_unlock(&cache.lock);\n}\n\nstatic void cache_invalidate_write(const char *path)\n{\n\tpthread_mutex_lock(&cache.lock);\n\tcache_purge(path);\n\tcache.write_ctr++;\n\tpthread_mutex_unlock(&cache.lock);\n}\n\nstatic void cache_invalidate_dir(const char *path)\n{\n\tpthread_mutex_lock(&cache.lock);\n\tcache_purge(path);\n\tcache_purge_parent(path);\n\tpthread_mutex_unlock(&cache.lock);\n}\n\nstatic int cache_del_children(const char *key, void *val_, const char *path)\n{\n\t(void) val_;\n\tif (strncmp(key, path, strlen(path)) == 0)\n\t\treturn TRUE;\n\telse\n\t\treturn FALSE;\n}\n\nstatic void cache_do_rename(const char *from, const char *to)\n{\n\tpthread_mutex_lock(&cache.lock);\n\tg_hash_table_foreach_remove(cache.table, (GHRFunc) cache_del_children,\n\t\t\t\t    (char *) from);\n\tcache_purge(from);\n\tcache_purge(to);\n\tcache_purge_parent(from);\n\tcache_purge_parent(to);\n\tpthread_mutex_unlock(&cache.lock);\n}\n\nstatic struct node *cache_get(const char *path)\n{\n\tstruct node *node = cache_lookup(path);\n\tif (node == NULL) {\n\t\tchar *pathcopy = g_strdup(path);\n\t\tnode = g_new0(struct node, 1);\n\t\tg_hash_table_insert(cache.table, pathcopy, node);\n\t}\n\treturn node;\n}\n\nvoid cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr)\n{\n\tstruct node *node;\n\n\tpthread_mutex_lock(&cache.lock);\n\tif (wrctr == cache.write_ctr) {\n\t\tnode = cache_get(path);\n\t\tnode->stat = *stbuf;\n\t\tnode->stat_valid = time(NULL) + cache.stat_timeout_secs;\n\t\tif (node->stat_valid > node->valid)\n\t\t\tnode->valid = node->stat_valid;\n\t\tcache_clean();\n\t}\n\tpthread_mutex_unlock(&cache.lock);\n}\n\nstatic void cache_add_dir(const char *path, GPtrArray *dir)\n{\n\tstruct node *node;\n\n\tpthread_mutex_lock(&cache.lock);\n\tnode = cache_get(path);\n\tif (node->dir != NULL) {\n\t\tg_ptr_array_free(node->dir, TRUE);\n  }\n\tnode->dir = dir;\n\tnode->dir_valid = time(NULL) + cache.dir_timeout_secs;\n\tif (node->dir_valid > node->valid)\n\t\tnode->valid = node->dir_valid;\n\tcache_clean();\n\tpthread_mutex_unlock(&cache.lock);\n}\n\nstatic size_t my_strnlen(const char *s, size_t maxsize)\n{\n\tconst char *p;\n\tfor (p = s; maxsize && *p; maxsize--, p++);\n\treturn p - s;\n}\n\nstatic void cache_add_link(const char *path, const char *link, size_t size)\n{\n\tstruct node *node;\n\n\tpthread_mutex_lock(&cache.lock);\n\tnode = cache_get(path);\n\tg_free(node->link);\n\tnode->link = g_strndup(link, my_strnlen(link, size-1));\n\tnode->link_valid = time(NULL) + cache.link_timeout_secs;\n\tif (node->link_valid > node->valid)\n\t\tnode->valid = node->link_valid;\n\tcache_clean();\n\tpthread_mutex_unlock(&cache.lock);\n}\n\nstatic int cache_get_attr(const char *path, struct stat *stbuf)\n{\n\tstruct node *node;\n\tint err = -EAGAIN;\n\tpthread_mutex_lock(&cache.lock);\n\tnode = cache_lookup(path);\n\tif (node != NULL) {\n\t\ttime_t now = time(NULL);\n\t\tif (node->stat_valid - now >= 0) {\n\t\t\t*stbuf = node->stat;\n\t\t\terr = 0;\n\t\t}\n\t}\n\tpthread_mutex_unlock(&cache.lock);\n\treturn err;\n}\n\nuint64_t cache_get_write_ctr(void)\n{\n\tuint64_t res;\n\n\tpthread_mutex_lock(&cache.lock);\n\tres = cache.write_ctr;\n\tpthread_mutex_unlock(&cache.lock);\n\n\treturn res;\n}\n\nstatic void *cache_init(struct fuse_conn_info *conn,\n                        struct fuse_config *cfg)\n{\n\tvoid *res;\n\tres = cache.next_oper->init(conn, cfg);\n\n\t// Cache requires a path for each request\n\tcfg->nullpath_ok = 0;\n\n\treturn res;\n}\n\nstatic int cache_getattr(const char *path, struct stat *stbuf,\n\t\t\t struct fuse_file_info *fi)\n{\n\tint err = cache_get_attr(path, stbuf);\n\tif (err) {\n\t\tuint64_t wrctr = cache_get_write_ctr();\n\t\terr = cache.next_oper->getattr(path, stbuf, fi);\n\t\tif (!err)\n\t\t\tcache_add_attr(path, stbuf, wrctr);\n\t}\n\treturn err;\n}\n\nstatic int cache_readlink(const char *path, char *buf, size_t size)\n{\n\tstruct node *node;\n\tint err;\n\n\tpthread_mutex_lock(&cache.lock);\n\tnode = cache_lookup(path);\n\tif (node != NULL) {\n\t\ttime_t now = time(NULL);\n\t\tif (node->link_valid - now >= 0) {\n\t\t\tstrncpy(buf, node->link, size-1);\n\t\t\tbuf[size-1] = '\\0';\n\t\t\tpthread_mutex_unlock(&cache.lock);\n\t\t\treturn 0;\n\t\t}\n\t}\n\tpthread_mutex_unlock(&cache.lock);\n\terr = cache.next_oper->readlink(path, buf, size);\n\tif (!err)\n\t\tcache_add_link(path, buf, size);\n\n\treturn err;\n}\n\n\nstatic int cache_opendir(const char *path, struct fuse_file_info *fi)\n{\n\t(void) path;\n\tstruct file_handle *cfi;\n\n\tcfi = malloc(sizeof(struct file_handle));\n\tif(cfi == NULL)\n\t\treturn -ENOMEM;\n\tcfi->is_open = 0;\n\tfi->fh = (unsigned long) cfi;\n\treturn 0;\n}\n\nstatic int cache_releasedir(const char *path, struct fuse_file_info *fi)\n{\n\tint err;\n\tstruct file_handle *cfi;\n\n\tcfi = (struct file_handle*) fi->fh;\n\n\tif(cfi->is_open) {\n\t\tfi->fh = cfi->fs_fh;\n\t\terr = cache.next_oper->releasedir(path, fi);\n\t} else\n\t\terr = 0;\n\n\tfree(cfi);\n\treturn err;\n}\n\nstatic int cache_dirfill (void *buf, const char *name,\n\t\t\t  const struct stat *stbuf, off_t off,\n\t\t\t  enum fuse_fill_dir_flags flags)\n{\n\tint err;\n\tstruct readdir_handle *ch;\n\n\tch = (struct readdir_handle*) buf;\n\terr = ch->filler(ch->buf, name, stbuf, off, flags);\n\tif (!err) {\n\t\tstruct cache_dirent *cdent = g_malloc(sizeof(struct cache_dirent));\n\t\tcdent->name = g_strdup(name);\n\t\tcdent->stat = *stbuf;\n\t\tg_ptr_array_add(ch->dir, cdent);\n\t\tif (stbuf->st_mode & S_IFMT) {\n\t\t\tchar *fullpath;\n\t\t\tconst char *basepath = !ch->path[1] ? \"\" : ch->path;\n\n\t\t\tfullpath = g_strdup_printf(\"%s/%s\", basepath, name);\n\t\t\tcache_add_attr(fullpath, stbuf, ch->wrctr);\n\t\t\tg_free(fullpath);\n\t\t}\n\t}\n\treturn err;\n}\n\nstatic int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,\n\t\t\t off_t offset, struct fuse_file_info *fi,\n\t\t\t enum fuse_readdir_flags flags)\n{\n\tstruct readdir_handle ch;\n\tstruct file_handle *cfi;\n\tint err;\n\tGPtrArray *dir;\n\tstruct node *node;\n\tstruct cache_dirent **cdent;\n\n\tassert(offset == 0);\n\n\tpthread_mutex_lock(&cache.lock);\n\tnode = cache_lookup(path);\n\tif (node != NULL && node->dir != NULL) {\n\t\ttime_t now = time(NULL);\n\t\tif (node->dir_valid - now >= 0) {\n\t\t\tfor(cdent = (struct cache_dirent**)node->dir->pdata; *cdent != NULL; cdent++) {\n\t\t\t\tfiller(buf, (*cdent)->name, &(*cdent)->stat, 0, 0);\n      }\n\t\t\tpthread_mutex_unlock(&cache.lock);\n\t\t\treturn 0;\n\t\t}\n\t}\n\tpthread_mutex_unlock(&cache.lock);\n\n\tcfi = (struct file_handle*) fi->fh;\n\tif(cfi->is_open)\n\t\tfi->fh = cfi->fs_fh;\n\telse {\n\t\tif(cache.next_oper->opendir) {\n\t\t\terr = cache.next_oper->opendir(path, fi);\n\t\t\tif(err)\n\t\t\t\treturn err;\n\t\t}\n\t\tcfi->is_open = 1;\n\t\tcfi->fs_fh = fi->fh;\n\t}\n\n\tch.path = path;\n\tch.buf = buf;\n\tch.filler = filler;\n\tch.dir = g_ptr_array_new();\n\tg_ptr_array_set_free_func(ch.dir, free_cache_dirent);\n\tch.wrctr = cache_get_write_ctr();\n\terr = cache.next_oper->readdir(path, &ch, cache_dirfill, offset, fi, flags);\n\tg_ptr_array_add(ch.dir, NULL);\n\tdir = ch.dir;\n\tif (!err) {\n\t\tcache_add_dir(path, dir);\n\t} else {\n\t\tg_ptr_array_free(dir, TRUE);\n\t}\n\n\treturn err;\n}\n\nstatic int cache_mknod(const char *path, mode_t mode, dev_t rdev)\n{\n\tint err = cache.next_oper->mknod(path, mode, rdev);\n\tif (!err)\n\t\tcache_invalidate_dir(path);\n\treturn err;\n}\n\nstatic int cache_mkdir(const char *path, mode_t mode)\n{\n\tint err = cache.next_oper->mkdir(path, mode);\n\tif (!err)\n\t\tcache_invalidate_dir(path);\n\treturn err;\n}\n\nstatic int cache_unlink(const char *path)\n{\n\tint err = cache.next_oper->unlink(path);\n\tif (!err)\n\t\tcache_invalidate_dir(path);\n\treturn err;\n}\n\nstatic int cache_rmdir(const char *path)\n{\n\tint err = cache.next_oper->rmdir(path);\n\tif (!err)\n\t\tcache_invalidate_dir(path);\n\treturn err;\n}\n\nstatic int cache_symlink(const char *from, const char *to)\n{\n\tint err = cache.next_oper->symlink(from, to);\n\tif (!err)\n\t\tcache_invalidate_dir(to);\n\treturn err;\n}\n\nstatic int cache_rename(const char *from, const char *to, unsigned int flags)\n{\n\tint err = cache.next_oper->rename(from, to, flags);\n\tif (!err)\n\t\tcache_do_rename(from, to);\n\treturn err;\n}\n\nstatic int cache_link(const char *from, const char *to)\n{\n\tint err = cache.next_oper->link(from, to);\n\tif (!err) {\n\t\tcache_invalidate(from);\n\t\tcache_invalidate_dir(to);\n\t}\n\treturn err;\n}\n\nstatic int cache_chmod(const char *path, mode_t mode,\n                       struct fuse_file_info *fi)\n{\n\tint err = cache.next_oper->chmod(path, mode, fi);\n\tif (!err)\n\t\tcache_invalidate(path);\n\treturn err;\n}\n\nstatic int cache_chown(const char *path, uid_t uid, gid_t gid,\n                       struct fuse_file_info *fi)\n{\n\tint err = cache.next_oper->chown(path, uid, gid, fi);\n\tif (!err)\n\t\tcache_invalidate(path);\n\treturn err;\n}\n\nstatic int cache_utimens(const char *path, const struct timespec tv[2],\n\t\t\t struct fuse_file_info *fi)\n{\n\tint err = cache.next_oper->utimens(path, tv, fi);\n\tif (!err)\n\t\tcache_invalidate(path);\n\treturn err;\n}\n\nstatic int cache_write(const char *path, const char *buf, size_t size,\n                       off_t offset, struct fuse_file_info *fi)\n{\n\tint res = cache.next_oper->write(path, buf, size, offset, fi);\n\tif (res >= 0)\n\t\tcache_invalidate_write(path);\n\treturn res;\n}\n\nstatic int cache_create(const char *path, mode_t mode,\n                        struct fuse_file_info *fi)\n{\n\tint err = cache.next_oper->create(path, mode, fi);\n\tif (!err)\n\t\tcache_invalidate_dir(path);\n\treturn err;\n}\n\nstatic int cache_truncate(const char *path, off_t size,\n\t\t\t  struct fuse_file_info *fi)\n{\n\tint err = cache.next_oper->truncate(path, size, fi);\n\tif (!err)\n\t\tcache_invalidate(path);\n\treturn err;\n}\n\nstatic void cache_fill(struct fuse_operations *oper,\n\t\t       struct fuse_operations *cache_oper)\n{\n\tcache_oper->access   = oper->access;\n\tcache_oper->chmod    = oper->chmod ? cache_chmod : NULL;\n\tcache_oper->chown    = oper->chown ? cache_chown : NULL;\n\tcache_oper->create   = oper->create ? cache_create : NULL;\n\tcache_oper->flush    = oper->flush;\n\tcache_oper->fsync    = oper->fsync;\n\tcache_oper->getattr  = oper->getattr ? cache_getattr : NULL;\n\tcache_oper->getxattr = oper->getxattr;\n\tcache_oper->init     = cache_init;\n\tcache_oper->link     = oper->link ? cache_link : NULL;\n\tcache_oper->listxattr = oper->listxattr;\n\tcache_oper->mkdir    = oper->mkdir ? cache_mkdir : NULL;\n\tcache_oper->mknod    = oper->mknod ? cache_mknod : NULL;\n\tcache_oper->open     = oper->open;\n\tcache_oper->opendir  = cache_opendir;\n\tcache_oper->read     = oper->read;\n\tcache_oper->readdir  = oper->readdir ? cache_readdir : NULL;\n\tcache_oper->readlink = oper->readlink ? cache_readlink : NULL;\n\tcache_oper->release  = oper->release;\n\tcache_oper->releasedir = cache_releasedir;\n\tcache_oper->removexattr = oper->removexattr;\n\tcache_oper->rename   = oper->rename ? cache_rename : NULL;\n\tcache_oper->rmdir    = oper->rmdir ? cache_rmdir : NULL;\n\tcache_oper->setxattr = oper->setxattr;\n\tcache_oper->statfs   = oper->statfs;\n\tcache_oper->symlink  = oper->symlink ? cache_symlink : NULL;\n\tcache_oper->truncate = oper->truncate ? cache_truncate : NULL;\n\tcache_oper->unlink   = oper->unlink ? cache_unlink : NULL;\n\tcache_oper->utimens  = oper->utimens ? cache_utimens : NULL;\n\tcache_oper->write    = oper->write ? cache_write : NULL;\n}\n\nstruct fuse_operations *cache_wrap(struct fuse_operations *oper)\n{\n\tstatic struct fuse_operations cache_oper;\n\tcache.next_oper = oper;\n\n\tcache_fill(oper, &cache_oper);\n\tpthread_mutex_init(&cache.lock, NULL);\n\tcache.table = g_hash_table_new_full(g_str_hash, g_str_equal,\n\t\t\t\t\t    g_free, free_node);\n\tif (cache.table == NULL) {\n\t\tfprintf(stderr, \"failed to create cache\\n\");\n\t\treturn NULL;\n\t}\n\treturn &cache_oper;\n}\n\nstatic const struct fuse_opt cache_opts[] = {\n\t{ \"dcache_timeout=%u\", offsetof(struct cache, stat_timeout_secs), 0 },\n\t{ \"dcache_timeout=%u\", offsetof(struct cache, dir_timeout_secs), 0 },\n\t{ \"dcache_timeout=%u\", offsetof(struct cache, link_timeout_secs), 0 },\n\t{ \"dcache_stat_timeout=%u\", offsetof(struct cache, stat_timeout_secs), 0 },\n\t{ \"dcache_dir_timeout=%u\", offsetof(struct cache, dir_timeout_secs), 0 },\n\t{ \"dcache_link_timeout=%u\", offsetof(struct cache, link_timeout_secs), 0 },\n\t{ \"dcache_max_size=%u\", offsetof(struct cache, max_size), 0 },\n\t{ \"dcache_clean_interval=%u\", offsetof(struct cache,\n\t\t\t\t\t       clean_interval_secs), 0 },\n\t{ \"dcache_min_clean_interval=%u\", offsetof(struct cache,\n\t\t\t\t\t\t   min_clean_interval_secs), 0 },\n\n\t/* For backwards compatibility */\n\t{ \"cache_timeout=%u\", offsetof(struct cache, stat_timeout_secs), 0 },\n\t{ \"cache_timeout=%u\", offsetof(struct cache, dir_timeout_secs), 0 },\n\t{ \"cache_timeout=%u\", offsetof(struct cache, link_timeout_secs), 0 },\n\t{ \"cache_stat_timeout=%u\", offsetof(struct cache, stat_timeout_secs), 0 },\n\t{ \"cache_dir_timeout=%u\", offsetof(struct cache, dir_timeout_secs), 0 },\n\t{ \"cache_link_timeout=%u\", offsetof(struct cache, link_timeout_secs), 0 },\n\t{ \"cache_max_size=%u\", offsetof(struct cache, max_size), 0 },\n\t{ \"cache_clean_interval=%u\", offsetof(struct cache,\n\t\t\t\t\t       clean_interval_secs), 0 },\n\t{ \"cache_min_clean_interval=%u\", offsetof(struct cache,\n\t\t\t\t\t\t   min_clean_interval_secs), 0 },\n\tFUSE_OPT_END\n};\n\nint cache_parse_options(struct fuse_args *args)\n{\n\tcache.stat_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS;\n\tcache.dir_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS;\n\tcache.link_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS;\n\tcache.max_size = DEFAULT_MAX_CACHE_SIZE;\n\tcache.clean_interval_secs = DEFAULT_CACHE_CLEAN_INTERVAL_SECS;\n\tcache.min_clean_interval_secs = DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS;\n\n\treturn fuse_opt_parse(args, &cache, cache_opts, NULL);\n}\n"
  },
  {
    "path": "cache.h",
    "content": "/*\n    Caching file system proxy\n    Copyright (C) 2004  Miklos Szeredi <miklos@szeredi.hu>\n\n    This program can be distributed under the terms of the GNU GPL.\n    See the file COPYING.\n*/\n\n#include <fuse.h>\n#include <fuse_opt.h>\n\nstruct fuse_operations *cache_wrap(struct fuse_operations *oper);\nint cache_parse_options(struct fuse_args *args);\nvoid cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr);\nvoid cache_invalidate(const char *path);\nuint64_t cache_get_write_ctr(void);\n"
  },
  {
    "path": "compat/darwin_compat.c",
    "content": "/*\n * Copyright (c) 2006-2008 Amit Singh/Google Inc.\n * Copyright (c) 2012 Anatol Pomozov\n * Copyright (c) 2011-2013 Benjamin Fleischer\n */\n\n#include \"darwin_compat.h\"\n\n#include <assert.h>\n#include <errno.h>\n#include <sys/types.h>\n\n/*\n * Semaphore implementation based on:\n *\n * Copyright (C) 2000,02 Free Software Foundation, Inc.\n * This file is part of the GNU C Library.\n * Written by Ga<EB>l Le Mignot <address@hidden>\n *\n * The GNU C Library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Library General Public License as\n * published by the Free Software Foundation; either version 2 of the\n * License, or (at your option) any later version.\n *\n * The GNU C Library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Library General Public License for more details.\n *\n * You should have received a copy of the GNU Library General Public\n * License along with the GNU C Library; see the file COPYING.LIB.  If not,\n * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,\n * Boston, MA 02111-1307, USA.\n */\n\n/* Semaphores */\n\n#define __SEM_ID_NONE  ((int)0x0)\n#define __SEM_ID_LOCAL ((int)0xcafef00d)\n\n/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_init.html */\nint\ndarwin_sem_init(darwin_sem_t *sem, int pshared, unsigned int value)\n{\n    if (pshared) {\n        errno = ENOSYS;\n        return -1;\n    }\n\n    sem->id = __SEM_ID_NONE;\n\n    if (pthread_cond_init(&sem->__data.local.count_cond, NULL)) {\n        goto cond_init_fail;\n    }\n\n    if (pthread_mutex_init(&sem->__data.local.count_lock, NULL)) {\n        goto mutex_init_fail;\n    }\n\n    sem->__data.local.count = value;\n    sem->id = __SEM_ID_LOCAL;\n\n    return 0;\n\nmutex_init_fail:\n\n    pthread_cond_destroy(&sem->__data.local.count_cond);\n\ncond_init_fail:\n\n    return -1;\n}\n\n/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_destroy.html */\nint\ndarwin_sem_destroy(darwin_sem_t *sem)\n{\n    int res = 0;\n\n    pthread_mutex_lock(&sem->__data.local.count_lock);\n\n    sem->id = __SEM_ID_NONE;\n    pthread_cond_broadcast(&sem->__data.local.count_cond);\n\n    if (pthread_cond_destroy(&sem->__data.local.count_cond)) {\n        res = -1;\n    }\n\n    pthread_mutex_unlock(&sem->__data.local.count_lock);\n\n    if (pthread_mutex_destroy(&sem->__data.local.count_lock)) {\n        res = -1;\n    }\n\n    return res;\n}\n\nint\ndarwin_sem_getvalue(darwin_sem_t *sem, unsigned int *sval)\n{\n    int res = 0;\n\n    pthread_mutex_lock(&sem->__data.local.count_lock);\n\n    if (sem->id != __SEM_ID_LOCAL) {\n        res = -1;\n        errno = EINVAL;\n    } else {\n        *sval = sem->__data.local.count;\n    }\n\n    pthread_mutex_unlock(&sem->__data.local.count_lock);\n\n    return res;\n}\n\n/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_post.html */\nint\ndarwin_sem_post(darwin_sem_t *sem)\n{\n    int res = 0;\n\n    pthread_mutex_lock(&sem->__data.local.count_lock);\n\n    if (sem->id != __SEM_ID_LOCAL) {\n        res = -1;\n        errno = EINVAL;\n    } else if (sem->__data.local.count < DARWIN_SEM_VALUE_MAX) {\n        sem->__data.local.count++;\n        if (sem->__data.local.count == 1) {\n            pthread_cond_signal(&sem->__data.local.count_cond);\n        }\n    } else {\n        errno = ERANGE;\n        res = -1;\n    }\n\n    pthread_mutex_unlock(&sem->__data.local.count_lock);\n\n    return res;\n}\n\n/* http://www.opengroup.org/onlinepubs/009695399/functions/sem_timedwait.html */\nint\ndarwin_sem_timedwait(darwin_sem_t *sem, const struct timespec *abs_timeout)\n{\n    int res = 0;\n\n    if (abs_timeout &&\n        (abs_timeout->tv_nsec < 0 || abs_timeout->tv_nsec >= 1000000000)) {\n        errno = EINVAL;\n        return -1;\n    }\n\n    pthread_cleanup_push((void(*)(void*))&pthread_mutex_unlock,\n                 &sem->__data.local.count_lock);\n\n    pthread_mutex_lock(&sem->__data.local.count_lock);\n\n    if (sem->id != __SEM_ID_LOCAL) {\n        errno = EINVAL;\n        res = -1;\n    } else {\n        if (!sem->__data.local.count) {\n            res = pthread_cond_timedwait(&sem->__data.local.count_cond,\n                             &sem->__data.local.count_lock,\n                             abs_timeout);\n        }\n        if (res) {\n            assert(res == ETIMEDOUT);\n            res = -1;\n            errno = ETIMEDOUT;\n        } else if (sem->id != __SEM_ID_LOCAL) {\n            res = -1;\n            errno = EINVAL;\n        } else {\n            sem->__data.local.count--;\n        }\n    }\n\n    pthread_cleanup_pop(1);\n\n    return res;\n}\n\n/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_trywait.html */\nint\ndarwin_sem_trywait(darwin_sem_t *sem)\n{\n    int res = 0;\n\n    pthread_mutex_lock(&sem->__data.local.count_lock);\n\n    if (sem->id != __SEM_ID_LOCAL) {\n        res = -1;\n        errno = EINVAL;\n    } else if (sem->__data.local.count) {\n        sem->__data.local.count--;\n    } else {\n        res = -1;\n        errno = EAGAIN;\n    }\n\n    pthread_mutex_unlock (&sem->__data.local.count_lock);\n\n    return res;\n}\n\n/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_wait.html */\nint\ndarwin_sem_wait(darwin_sem_t *sem)\n{\n    int res = 0;\n\n    pthread_cleanup_push((void(*)(void*))&pthread_mutex_unlock,\n                 &sem->__data.local.count_lock);\n\n    pthread_mutex_lock(&sem->__data.local.count_lock);\n\n    if (sem->id != __SEM_ID_LOCAL) {\n        errno = EINVAL;\n        res = -1;\n    } else {\n        if (!sem->__data.local.count) {\n            pthread_cond_wait(&sem->__data.local.count_cond,\n                      &sem->__data.local.count_lock);\n            if (!sem->__data.local.count) {\n                /* spurious wakeup, assume it is an interruption */\n                res = -1;\n                errno = EINTR;\n                goto out;\n            }\n        }\n        if (sem->id != __SEM_ID_LOCAL) {\n            res = -1;\n            errno = EINVAL;\n        } else {\n            sem->__data.local.count--;\n        }\n    }\n\nout:\n    pthread_cleanup_pop(1);\n\n    return res;\n}\n"
  },
  {
    "path": "compat/darwin_compat.h",
    "content": "/*\n * Copyright (c) 2006-2008 Amit Singh/Google Inc.\n * Copyright (c) 2011-2013 Benjamin Fleischer\n */\n\n#ifndef _DARWIN_COMPAT_\n#define _DARWIN_COMPAT_\n\n#include <pthread.h>\n\n/* Semaphores */\n\ntypedef struct darwin_sem {\n    int id;\n    union {\n        struct\n        {\n            unsigned int    count;\n            pthread_mutex_t count_lock;\n            pthread_cond_t  count_cond;\n        } local;\n    } __data;\n} darwin_sem_t;\n\n#define DARWIN_SEM_VALUE_MAX ((int32_t)32767)\n\nint darwin_sem_init(darwin_sem_t *sem, int pshared, unsigned int value);\nint darwin_sem_destroy(darwin_sem_t *sem);\nint darwin_sem_getvalue(darwin_sem_t *sem, unsigned int *value);\nint darwin_sem_post(darwin_sem_t *sem);\nint darwin_sem_timedwait(darwin_sem_t *sem, const struct timespec *abs_timeout);\nint darwin_sem_trywait(darwin_sem_t *sem);\nint darwin_sem_wait(darwin_sem_t *sem);\n\n/* Caller must not include <semaphore.h> */\n\ntypedef darwin_sem_t sem_t;\n\n#define sem_init(s, p, v)   darwin_sem_init(s, p, v)\n#define sem_destroy(s)      darwin_sem_destroy(s)\n#define sem_getvalue(s, v)  darwin_sem_getvalue(s, v)\n#define sem_post(s)         darwin_sem_post(s)\n#define sem_timedwait(s, t) darwin_sem_timedwait(s, t)\n#define sem_trywait(s)      darwin_sem_trywait(s)\n#define sem_wait(s)         darwin_sem_wait(s)\n\n#define SEM_VALUE_MAX       DARWIN_SEM_VALUE_MAX\n\n#endif /* _DARWIN_COMPAT_ */\n"
  },
  {
    "path": "make_release_tarball.sh",
    "content": "#!/bin/sh\n#\n# Create tarball from Git tag, removing and adding\n# some files.\n#\n\nset -e\n\nif [ -z \"$1\" ]; then\n    TAG=\"$(git tag --list 'sshfs-3*' --sort=-taggerdate | head -1)\"\nelse\n    TAG=\"$1\"\nfi\n\necho \"Creating release tarball for ${TAG}...\"\n\nmkdir \"${TAG}\"\ngit archive --format=tar \"${TAG}\" | tar -x \"--directory=${TAG}\"\nfind \"${TAG}\" -name .gitignore -delete\nrm \"${TAG}/make_release_tarball.sh\" \\\n   \"${TAG}/.travis.yml\" \\\n   \"${TAG}/test/travis-build.sh\" \\\n   \"${TAG}/test/travis-install.sh\"\ntar -cJf \"${TAG}.tar.xz\" \"${TAG}/\"\ngpg --armor --detach-sign \"${TAG}.tar.xz\"\n\nPREV_TAG=\"$(git tag --list 'sshfs-3*' --sort=-taggerdate --merged \"${TAG}^\"| head -1)\"\necho \"Contributors from ${PREV_TAG} to ${TAG}:\"\ngit log --pretty=\"format:%an <%aE>\" \"${PREV_TAG}..${TAG}\" | sort -u\n"
  },
  {
    "path": "meson.build",
    "content": "project('sshfs', 'c', version: '3.7.5',\n        meson_version: '>= 0.40',\n        default_options: [ 'buildtype=debugoptimized' ])\n\nadd_global_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H',\n                     '-Wall', '-Wextra', '-Wno-sign-compare',\n                     '-Wmissing-declarations', '-Wwrite-strings',\n                     language: 'c')\n\n# Some (stupid) GCC versions warn about unused return values even when they are\n# casted to void. This makes -Wunused-result pretty useless, since there is no\n# way to suppress the warning when we really *want* to ignore the value.\ncc = meson.get_compiler('c')\ncode = '''\n__attribute__((warn_unused_result)) int get_4() {\n    return 4;\n}\nint main(void) {\n    (void) get_4();\n    return 0;\n}'''\nif not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ])\n     message('Compiler warns about unused result even when casting to void')\n     add_global_arguments('-Wno-unused-result', language: 'c')\nendif\n\n\nrst2man = find_program('rst2man', 'rst2man.py', required: false)\n\ncfg = configuration_data()\n\ncfg.set_quoted('PACKAGE_VERSION', meson.project_version())\n\ninclude_dirs = [ include_directories('.') ]\nsshfs_sources = ['sshfs.c', 'cache.c']\nif target_machine.system() == 'darwin'\n  add_global_arguments('-DFUSE_DARWIN_ENABLE_EXTENSIONS=0', language: 'c')\n  cfg.set_quoted('IDMAP_DEFAULT', 'user')\n  sshfs_sources += [ 'compat/darwin_compat.c' ]\n  include_dirs += [ include_directories('compat') ]\nelse\n  cfg.set_quoted('IDMAP_DEFAULT', 'none')\nendif\n\nconfigure_file(output: 'config.h',\n               configuration : cfg)\n\nsshfs_deps = [ dependency('fuse3', version: '>= 3.1.0'),\n               dependency('glib-2.0'),\n               dependency('gthread-2.0') ]\n\nexecutable('sshfs', sshfs_sources,\n           include_directories: include_dirs,\n           dependencies: sshfs_deps,\n           c_args: ['-DFUSE_USE_VERSION=31'],\n           install: true,\n           install_dir: get_option('bindir'))\n\nif rst2man.found()\n    custom_target('manpages', input: [ 'sshfs.rst' ], output: [ 'sshfs.1' ],\n                  command: [rst2man, '@INPUT@', '@OUTPUT@'], install: true,\n                  install_dir: join_paths(get_option('mandir'), 'man1'))\nelse\n    message('rst2man not found, not building manual page.')\nendif\n\nmeson.add_install_script('utils/install_helper.sh',\n                         get_option('sbindir'),\n                         get_option('bindir'))\n\n\nsubdir('test')\n"
  },
  {
    "path": "sshfs.c",
    "content": "/*\n  SSH file system\n  Copyright (C) 2004  Miklos Szeredi <miklos@szeredi.hu>\n\n  This program can be distributed under the terms of the GNU GPL.\n  See the file COPYING.\n*/\n\n#define _GNU_SOURCE /* avoid implicit declaration of *pt* functions */\n#include \"config.h\"\n\n#include <fuse.h>\n#include <fuse_opt.h>\n#if !defined(__CYGWIN__)\n#  include <fuse_lowlevel.h>\n#endif\n#include <assert.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <string.h>\n#include <stdint.h>\n#include <errno.h>\n#ifndef __APPLE__\n#  include <semaphore.h>\n#endif\n#include <pthread.h>\n#include <netdb.h>\n#include <signal.h>\n#include <sys/uio.h>\n#include <sys/types.h>\n#include <sys/time.h>\n#include <sys/wait.h>\n#include <sys/socket.h>\n#include <sys/utsname.h>\n#include <sys/mman.h>\n#include <poll.h>\n#include <netinet/in.h>\n#include <netinet/tcp.h>\n#include <glib.h>\n#include <pwd.h>\n#include <grp.h>\n#include <limits.h>\n#ifdef __APPLE__\n#  include <strings.h>\n#  include <libgen.h>\n#  include <darwin_compat.h>\n#endif\n#ifdef __linux__\n#  include <linux/vm_sockets.h>\n#endif\n\n#include \"cache.h\"\n\n#ifndef MAP_LOCKED\n#  define MAP_LOCKED 0\n#endif\n\n#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)\n#  define MAP_ANONYMOUS MAP_ANON\n#endif\n\n\n#define SSH_FXP_INIT                1\n#define SSH_FXP_VERSION             2\n#define SSH_FXP_OPEN                3\n#define SSH_FXP_CLOSE               4\n#define SSH_FXP_READ                5\n#define SSH_FXP_WRITE               6\n#define SSH_FXP_LSTAT               7\n#define SSH_FXP_FSTAT               8\n#define SSH_FXP_SETSTAT             9\n#define SSH_FXP_FSETSTAT           10\n#define SSH_FXP_OPENDIR            11\n#define SSH_FXP_READDIR            12\n#define SSH_FXP_REMOVE             13\n#define SSH_FXP_MKDIR              14\n#define SSH_FXP_RMDIR              15\n#define SSH_FXP_REALPATH           16\n#define SSH_FXP_STAT               17\n#define SSH_FXP_RENAME             18\n#define SSH_FXP_READLINK           19\n#define SSH_FXP_SYMLINK            20\n#define SSH_FXP_STATUS            101\n#define SSH_FXP_HANDLE            102\n#define SSH_FXP_DATA              103\n#define SSH_FXP_NAME              104\n#define SSH_FXP_ATTRS             105\n#define SSH_FXP_EXTENDED          200\n#define SSH_FXP_EXTENDED_REPLY    201\n\n#define SSH_FILEXFER_ATTR_SIZE          0x00000001\n#define SSH_FILEXFER_ATTR_UIDGID        0x00000002\n#define SSH_FILEXFER_ATTR_PERMISSIONS   0x00000004\n#define SSH_FILEXFER_ATTR_ACMODTIME     0x00000008\n#define SSH_FILEXFER_ATTR_EXTENDED      0x80000000\n\n#define SSH_FX_OK                            0\n#define SSH_FX_EOF                           1\n#define SSH_FX_NO_SUCH_FILE                  2\n#define SSH_FX_PERMISSION_DENIED             3\n#define SSH_FX_FAILURE                       4\n#define SSH_FX_BAD_MESSAGE                   5\n#define SSH_FX_NO_CONNECTION                 6\n#define SSH_FX_CONNECTION_LOST               7\n#define SSH_FX_OP_UNSUPPORTED                8\n\n#define SSH_FXF_READ            0x00000001\n#define SSH_FXF_WRITE           0x00000002\n#define SSH_FXF_APPEND          0x00000004\n#define SSH_FXF_CREAT           0x00000008\n#define SSH_FXF_TRUNC           0x00000010\n#define SSH_FXF_EXCL            0x00000020\n\n/* statvfs@openssh.com f_flag flags */\n#define SSH2_FXE_STATVFS_ST_RDONLY\t0x00000001\n#define SSH2_FXE_STATVFS_ST_NOSUID\t0x00000002\n\n#define SFTP_EXT_POSIX_RENAME \"posix-rename@openssh.com\"\n#define SFTP_EXT_STATVFS \"statvfs@openssh.com\"\n#define SFTP_EXT_HARDLINK \"hardlink@openssh.com\"\n#define SFTP_EXT_FSYNC \"fsync@openssh.com\"\n\n#define PROTO_VERSION 3\n\n#define MY_EOF 1\n\n#define MAX_REPLY_LEN (1 << 17)\n\n#define RENAME_TEMP_CHARS 8\n\n#define SFTP_SERVER_PATH \"/usr/lib/sftp-server\"\n\n/* Asynchronous readdir parameters */\n#define READDIR_START 2\n#define READDIR_MAX 32\n\n#define MAX_PASSWORD 1024\n\n/*\n   Handling of multiple SFTP connections\n   --------------------------------------\n\n   An SFTP server is free to return responses to outstanding requests in arbitrary\n   order. However, execution of requests may only be re-ordered and parallelized as long\n   as \"the results in the responses will be the same as if [the client] had sent the\n   requests one at a time and waited for the response in each case\".\n   (https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.1).\n\n   When using multiple connections, this requirement applies independently for each\n   connection. We therefore have to make sure in SSHFS that the way in which we distribute\n   requests between connections does not affect the responses that we get.\n\n   In general, this is a tricky problem to solve since for each incoming request we have\n   to determine which other in-flight requests may interact with it, and then either\n   transmit the request through the same connection or (if there are multiple connections\n   involved) wait for the other requests to complete. This means that e.g. a readdir\n   request would have to block on most other activity in the same directory, eliminating a\n   major advantage of using multiple connections.\n\n   In practice, we can luckily take advantage of the knowledge that most FUSE requests are\n   the result of (synchronous) syscalls from userspace that will block until the\n   corresponding FUSE response has been sent.\n\n   If -o sshfs_sync is used, SSHFS always waits for the SFTP server response before\n   returning a FUSE response to userspace. If userspace makes concurrent system calls,\n   there is no ordering guarantee in the first place, so we do not have to worry about\n   (re-)ordering within SSHFS either.\n\n   For requests that originate in the kernel (rather than userspace), the situation is\n   slightly different. Transmission of FUSE requests and responses is decoupled (there are\n   no synchronous calls) and there is no formal specification that defines if reordering\n   is permitted. However, the Linux kernel seems to avoid submitting any concurrent\n   requests that would give different results depending on execution order and (as of\n   kernel 4.20 with writeback caching disabled) the only kind of kernel originated\n   requests are read() requests for read-ahead. Since libfuse internally uses multiple\n   threads, SSHFS does not necessarily receive requests in the order in which they were\n   sent by the kernel. Unless there is a major bug in FUSE, there is therefore no need to\n   worry about correct sequencing of such calls even when using multiple SFTP connections.\n\n   If -o sshfs_sync is *not* used, then write() syscalls will return to userspace before\n   SSHFS has received responses from the SFTP server. If userspace then issues a second\n   syscall related to the same file (and only one connection is in-use), SFTP ordering\n   guarantees will ensure that the response takes into account the preceding writes. If\n   multiple connections are in use, this has to be ensured by SSHFS instead.\n\n   The easiest way to do so would be to bind specific SFTP connections to file\n   handles. Unfortunately, not all requests for the same dentry are guaranteed to come\n   from the same file handle and some requests may come without any file handle. We\n   therefore maintain a separate mapping from currently open files to SFTP connections. If\n   a request comes in for a path contained in sshfs.conntab and its result could be\n   changed by a pending write() operation, it will always be executed with the\n   associated SFTP connection.\n\n   There are additional subtleties for requests that affect multiple paths.  For example,\n   if both source and destination of a rename() request are currently open, which\n   connection should be used?\n\n   This problem is again hard in general, but solvable since we only have to worry about\n   the effects of pending write() calls. For rename() and link(), it does not matter if a\n   pending write is executed before or after the operation. For readdir(), it is possible\n   that a pending write() will change the length of the file. However, SSHFS currently\n   does not return attribute information for readdir(), so this does not pose problems\n   either. Should SSHFS implement a readdirplus() handler (which provides file names and\n   attributes) this is a problem that will need to be solved.\n*/\n\n\n#ifdef __APPLE__\n   static char sshfs_program_path[PATH_MAX] = { 0 };\n#endif /* __APPLE__ */\n\nstruct conn {\n\tpthread_mutex_t lock_write;\n\tint processing_thread_started;\n\tint rfd;\n\tint wfd;\n\tint connver;\n\tint req_count;\n\tint dir_count;\n\tint file_count;\n};\n\nstruct buffer {\n\tuint8_t *p;\n\tsize_t len;\n\tsize_t size;\n};\n\nstruct dir_handle {\n\tstruct buffer buf;\n\tstruct conn *conn;\n};\n\nstruct list_head {\n\tstruct list_head *prev;\n\tstruct list_head *next;\n};\n\nstruct request;\ntypedef void (*request_func)(struct request *);\n\nstruct request {\n\tunsigned int want_reply;\n\tsem_t ready;\n\tuint8_t reply_type;\n\tuint32_t id;\n\tint replied;\n\tint error;\n\tstruct buffer reply;\n\tstruct timeval start;\n\tvoid *data;\n\trequest_func end_func;\n\tsize_t len;\n\tstruct list_head list;\n\tstruct conn *conn;\n};\n\nstruct sshfs_io {\n\tint num_reqs;\n\tpthread_cond_t finished;\n\tint error;\n};\n\nstruct read_req {\n\tstruct sshfs_io *sio;\n\tstruct list_head list;\n\tstruct buffer data;\n\tsize_t size;\n\tssize_t res;\n};\n\nstruct read_chunk {\n\toff_t offset;\n\tsize_t size;\n\tint refs;\n\tlong modifver;\n\tstruct list_head reqs;\n\tstruct sshfs_io sio;\n};\n\nstruct sshfs_file {\n\tstruct buffer handle;\n\tstruct list_head write_reqs;\n\tpthread_cond_t write_finished;\n\tint write_error;\n\tstruct read_chunk *readahead;\n\toff_t next_pos;\n\tint is_seq;\n\tstruct conn *conn;\n\tint connver;\n\tint modifver;\n};\n\nstruct conntab_entry {\n\tunsigned refcount;\n\tstruct conn *conn;\n};\n\nstruct sshfs {\n\tchar *directport;\n\tchar *ssh_command;\n\tchar *sftp_server;\n\tstruct fuse_args ssh_args;\n\tchar *workarounds;\n\tint rename_workaround;\n\tint renamexdev_workaround;\n\tint truncate_workaround;\n\tint buflimit_workaround;\n\tint unrel_append;\n\tint fstat_workaround;\n\tint createmode_workaround;\n\tint transform_symlinks;\n\tint follow_symlinks;\n\tint no_check_root;\n\tint detect_uid;\n\tint idmap;\n\tint nomap;\n\tint disable_hardlink;\n\tint dir_cache;\n\tint show_version;\n\tint show_help;\n\tint singlethread;\n\tchar *mountpoint;\n\tchar *uid_file;\n\tchar *gid_file;\n\tGHashTable *uid_map;\n\tGHashTable *gid_map;\n\tGHashTable *r_uid_map;\n\tGHashTable *r_gid_map;\n\tunsigned max_read;\n\tunsigned max_write;\n\tunsigned ssh_ver;\n\tint sync_write;\n\tint sync_read;\n\tint sync_readdir;\n\tint direct_io;\n\tint debug;\n\tint verbose;\n\tint foreground;\n\tint reconnect;\n\tint delay_connect;\n\tint passive;\n\tchar *host;\n\tchar *base_path;\n\tGHashTable *reqtab;\n\tGHashTable *conntab;\n\tpthread_mutex_t lock;\n\tunsigned int randseed;\n\tint max_conns;\n\tchar *vsock;\n\tstruct conn *conns;\n\tint ptyfd;\n\tint ptypassivefd;\n\tint connvers;\n\tint server_version;\n\tunsigned remote_uid;\n\tunsigned local_uid;\n\tunsigned remote_gid;\n\tunsigned local_gid;\n\tint remote_uid_detected;\n\tunsigned blksize;\n\tchar *progname;\n\tlong modifver;\n\tunsigned outstanding_len;\n\tunsigned max_outstanding_len;\n\tpthread_cond_t outstanding_cond;\n\tint password_stdin;\n\tchar *password;\n\tint ext_posix_rename;\n\tint ext_statvfs;\n\tint ext_hardlink;\n\tint ext_fsync;\n\tstruct fuse_operations *op;\n\n\t/* statistics */\n\tuint64_t bytes_sent;\n\tuint64_t bytes_received;\n\tuint64_t num_sent;\n\tuint64_t num_received;\n\tunsigned int min_rtt;\n\tunsigned int max_rtt;\n\tuint64_t total_rtt;\n\tunsigned int num_connect;\n};\n\nstatic struct sshfs sshfs;\n\nstatic const char *ssh_opts[] = {\n\t\"AddressFamily\",\n\t\"BatchMode\",\n\t\"BindAddress\",\n\t\"BindInterface\",\n\t\"CertificateFile\",\n\t\"ChallengeResponseAuthentication\",\n\t\"CheckHostIP\",\n\t\"Cipher\",\n\t\"Ciphers\",\n\t\"Compression\",\n\t\"CompressionLevel\",\n\t\"ConnectionAttempts\",\n\t\"ConnectTimeout\",\n\t\"ControlMaster\",\n\t\"ControlPath\",\n\t\"ControlPersist\",\n\t\"FingerprintHash\",\n\t\"GlobalKnownHostsFile\",\n\t\"GSSAPIAuthentication\",\n\t\"GSSAPIDelegateCredentials\",\n\t\"HostbasedAuthentication\",\n\t\"HostbasedKeyTypes\",\n\t\"HostKeyAlgorithms\",\n\t\"HostKeyAlias\",\n\t\"HostName\",\n\t\"IdentitiesOnly\",\n\t\"IdentityFile\",\n\t\"IdentityAgent\",\n\t\"IPQoS\",\n\t\"KbdInteractiveAuthentication\",\n\t\"KbdInteractiveDevices\",\n\t\"KexAlgorithms\",\n\t\"LocalCommand\",\n\t\"LogLevel\",\n\t\"MACs\",\n\t\"NoHostAuthenticationForLocalhost\",\n\t\"NumberOfPasswordPrompts\",\n\t\"PasswordAuthentication\",\n\t\"PermitLocalCommand\",\n\t\"PKCS11Provider\",\n\t\"Port\",\n\t\"PreferredAuthentications\",\n\t\"ProxyCommand\",\n\t\"ProxyJump\",\n\t\"ProxyUseFdpass\",\n\t\"PubkeyAcceptedKeyTypes\",\n\t\"PubkeyAuthentication\",\n\t\"RekeyLimit\",\n\t\"RevokedHostKeys\",\n\t\"RhostsRSAAuthentication\",\n\t\"RSAAuthentication\",\n\t\"ServerAliveCountMax\",\n\t\"ServerAliveInterval\",\n\t\"SmartcardDevice\",\n\t\"StrictHostKeyChecking\",\n\t\"TCPKeepAlive\",\n\t\"UpdateHostKeys\",\n\t\"UsePrivilegedPort\",\n\t\"UserKnownHostsFile\",\n\t\"VerifyHostKeyDNS\",\n\t\"VisualHostKey\",\n\tNULL,\n};\n\nenum {\n\tKEY_PORT,\n\tKEY_COMPRESS,\n\tKEY_CONFIGFILE,\n};\n\nenum {\n\tIDMAP_NONE,\n\tIDMAP_USER,\n\tIDMAP_FILE,\n};\n\nenum {\n\tNOMAP_IGNORE,\n\tNOMAP_ERROR,\n};\n\n#define SSHFS_OPT(t, p, v) { t, offsetof(struct sshfs, p), v }\n\nstatic struct fuse_opt sshfs_opts[] = {\n\tSSHFS_OPT(\"directport=%s\",     directport, 0),\n\tSSHFS_OPT(\"ssh_command=%s\",    ssh_command, 0),\n\tSSHFS_OPT(\"sftp_server=%s\",    sftp_server, 0),\n\tSSHFS_OPT(\"max_read=%u\",       max_read, 0),\n\tSSHFS_OPT(\"max_write=%u\",      max_write, 0),\n\tSSHFS_OPT(\"ssh_protocol=%u\",   ssh_ver, 0),\n\tSSHFS_OPT(\"-1\",                ssh_ver, 1),\n\tSSHFS_OPT(\"workaround=%s\",     workarounds, 0),\n\tSSHFS_OPT(\"idmap=none\",        idmap, IDMAP_NONE),\n\tSSHFS_OPT(\"idmap=user\",        idmap, IDMAP_USER),\n\tSSHFS_OPT(\"idmap=file\",        idmap, IDMAP_FILE),\n\tSSHFS_OPT(\"uidfile=%s\",        uid_file, 0),\n\tSSHFS_OPT(\"gidfile=%s\",        gid_file, 0),\n\tSSHFS_OPT(\"nomap=ignore\",      nomap, NOMAP_IGNORE),\n\tSSHFS_OPT(\"nomap=error\",       nomap, NOMAP_ERROR),\n\tSSHFS_OPT(\"sshfs_sync\",        sync_write, 1),\n\tSSHFS_OPT(\"no_readahead\",      sync_read, 1),\n\tSSHFS_OPT(\"sync_readdir\",      sync_readdir, 1),\n\tSSHFS_OPT(\"sshfs_debug\",       debug, 1),\n\tSSHFS_OPT(\"sshfs_verbose\",     verbose, 1),\n\tSSHFS_OPT(\"reconnect\",         reconnect, 1),\n\tSSHFS_OPT(\"transform_symlinks\", transform_symlinks, 1),\n\tSSHFS_OPT(\"follow_symlinks\",   follow_symlinks, 1),\n\tSSHFS_OPT(\"no_check_root\",     no_check_root, 1),\n\tSSHFS_OPT(\"password_stdin\",    password_stdin, 1),\n\tSSHFS_OPT(\"delay_connect\",     delay_connect, 1),\n\tSSHFS_OPT(\"slave\",             passive, 1),\n\tSSHFS_OPT(\"passive\",           passive, 1),\n\tSSHFS_OPT(\"disable_hardlink\",  disable_hardlink, 1),\n\tSSHFS_OPT(\"dir_cache=yes\", dir_cache, 1),\n\tSSHFS_OPT(\"dir_cache=no\",  dir_cache, 0),\n\tSSHFS_OPT(\"direct_io\",  direct_io, 1),\n\tSSHFS_OPT(\"max_conns=%u\",  max_conns, 1),\n\tSSHFS_OPT(\"vsock=%s\",      vsock, 0),\n\n\tSSHFS_OPT(\"-h\",\t\tshow_help, 1),\n\tSSHFS_OPT(\"--help\",\tshow_help, 1),\n\tSSHFS_OPT(\"-V\",\t\tshow_version, 1),\n\tSSHFS_OPT(\"--version\",\tshow_version, 1),\n\tSSHFS_OPT(\"-d\",\t\tdebug, 1),\n\tSSHFS_OPT(\"debug\",\tdebug, 1),\n\tSSHFS_OPT(\"-v\",\t\tverbose, 1),\n\tSSHFS_OPT(\"verbose\",\tverbose, 1),\n\tSSHFS_OPT(\"-f\",\t\tforeground, 1),\n\tSSHFS_OPT(\"-s\",\t\tsinglethread, 1),\n\n\tFUSE_OPT_KEY(\"-p \",            KEY_PORT),\n\tFUSE_OPT_KEY(\"-C\",             KEY_COMPRESS),\n\tFUSE_OPT_KEY(\"-F \",            KEY_CONFIGFILE),\n\n\t/* For backwards compatibility */\n\tSSHFS_OPT(\"cache=yes\", dir_cache, 1),\n\tSSHFS_OPT(\"cache=no\",  dir_cache, 0),\n\n\tFUSE_OPT_KEY(\"writeback_cache=no\", FUSE_OPT_KEY_DISCARD),\n\tFUSE_OPT_KEY(\"unreliable_append\", FUSE_OPT_KEY_DISCARD),\n\n\t/* These may come in from /etc/fstab - we just ignore them */\n\tFUSE_OPT_KEY(\"auto\", FUSE_OPT_KEY_DISCARD),\n\tFUSE_OPT_KEY(\"noauto\", FUSE_OPT_KEY_DISCARD),\n\tFUSE_OPT_KEY(\"user\", FUSE_OPT_KEY_DISCARD),\n\tFUSE_OPT_KEY(\"nouser\", FUSE_OPT_KEY_DISCARD),\n\tFUSE_OPT_KEY(\"users\", FUSE_OPT_KEY_DISCARD),\n\tFUSE_OPT_KEY(\"_netdev\", FUSE_OPT_KEY_DISCARD),\n\n\tFUSE_OPT_END\n};\n\nstatic struct fuse_opt workaround_opts[] = {\n\tSSHFS_OPT(\"none\",       rename_workaround, 0),\n\tSSHFS_OPT(\"none\",       truncate_workaround, 0),\n\tSSHFS_OPT(\"none\",       buflimit_workaround, 0),\n\tSSHFS_OPT(\"none\",       fstat_workaround, 0),\n\tSSHFS_OPT(\"rename\",     rename_workaround, 1),\n\tSSHFS_OPT(\"norename\",   rename_workaround, 0),\n\tSSHFS_OPT(\"renamexdev\",   renamexdev_workaround, 1),\n\tSSHFS_OPT(\"norenamexdev\", renamexdev_workaround, 0),\n\tSSHFS_OPT(\"truncate\",   truncate_workaround, 1),\n\tSSHFS_OPT(\"notruncate\", truncate_workaround, 0),\n\tSSHFS_OPT(\"buflimit\",   buflimit_workaround, 1),\n\tSSHFS_OPT(\"nobuflimit\", buflimit_workaround, 0),\n\tSSHFS_OPT(\"fstat\",      fstat_workaround, 1),\n\tSSHFS_OPT(\"nofstat\",    fstat_workaround, 0),\n\tSSHFS_OPT(\"createmode\",   createmode_workaround, 1),\n\tSSHFS_OPT(\"nocreatemode\", createmode_workaround, 0),\n\tFUSE_OPT_END\n};\n\n#define DEBUG(format, args...)\t\t\t\t\t\t\\\n\tdo { if (sshfs.debug) fprintf(stderr, format, args); } while(0)\n\nstatic const char *type_name(uint8_t type)\n{\n\tswitch(type) {\n\tcase SSH_FXP_INIT:           return \"INIT\";\n\tcase SSH_FXP_VERSION:        return \"VERSION\";\n\tcase SSH_FXP_OPEN:           return \"OPEN\";\n\tcase SSH_FXP_CLOSE:          return \"CLOSE\";\n\tcase SSH_FXP_READ:           return \"READ\";\n\tcase SSH_FXP_WRITE:          return \"WRITE\";\n\tcase SSH_FXP_LSTAT:          return \"LSTAT\";\n\tcase SSH_FXP_FSTAT:          return \"FSTAT\";\n\tcase SSH_FXP_SETSTAT:        return \"SETSTAT\";\n\tcase SSH_FXP_FSETSTAT:       return \"FSETSTAT\";\n\tcase SSH_FXP_OPENDIR:        return \"OPENDIR\";\n\tcase SSH_FXP_READDIR:        return \"READDIR\";\n\tcase SSH_FXP_REMOVE:         return \"REMOVE\";\n\tcase SSH_FXP_MKDIR:          return \"MKDIR\";\n\tcase SSH_FXP_RMDIR:          return \"RMDIR\";\n\tcase SSH_FXP_REALPATH:       return \"REALPATH\";\n\tcase SSH_FXP_STAT:           return \"STAT\";\n\tcase SSH_FXP_RENAME:         return \"RENAME\";\n\tcase SSH_FXP_READLINK:       return \"READLINK\";\n\tcase SSH_FXP_SYMLINK:        return \"SYMLINK\";\n\tcase SSH_FXP_STATUS:         return \"STATUS\";\n\tcase SSH_FXP_HANDLE:         return \"HANDLE\";\n\tcase SSH_FXP_DATA:           return \"DATA\";\n\tcase SSH_FXP_NAME:           return \"NAME\";\n\tcase SSH_FXP_ATTRS:          return \"ATTRS\";\n\tcase SSH_FXP_EXTENDED:       return \"EXTENDED\";\n\tcase SSH_FXP_EXTENDED_REPLY: return \"EXTENDED_REPLY\";\n\tdefault:                     return \"???\";\n\t}\n}\n\n#define container_of(ptr, type, member) ({\t\t\t\t\\\n\t\t\tconst typeof( ((type *)0)->member ) *__mptr = (ptr); \\\n\t\t\t(type *)( (char *)__mptr - offsetof(type,member) );})\n\n#define list_entry(ptr, type, member)\t\t\\\n\tcontainer_of(ptr, type, member)\n\nstatic void list_init(struct list_head *head)\n{\n\thead->next = head;\n\thead->prev = head;\n}\n\nstatic void list_add(struct list_head *new, struct list_head *head)\n{\n\tstruct list_head *prev = head;\n\tstruct list_head *next = head->next;\n\tnext->prev = new;\n\tnew->next = next;\n\tnew->prev = prev;\n\tprev->next = new;\n}\n\nstatic void list_del(struct list_head *entry)\n{\n\tstruct list_head *prev = entry->prev;\n\tstruct list_head *next = entry->next;\n\tnext->prev = prev;\n\tprev->next = next;\n\n}\n\nstatic int list_empty(const struct list_head *head)\n{\n\treturn head->next == head;\n}\n\n/* given a pointer to the uid/gid, and the mapping table, remap the\n * uid/gid, if necessary */\nstatic inline int translate_id(uint32_t *id, GHashTable *map)\n{\n\tgpointer id_p;\n\tif (g_hash_table_lookup_extended(map, GUINT_TO_POINTER(*id), NULL, &id_p)) {\n\t\t*id = GPOINTER_TO_UINT(id_p);\n\t\treturn 0;\n\t}\n\tswitch (sshfs.nomap) {\n\tcase NOMAP_ERROR: return -1;\n\tcase NOMAP_IGNORE: return 0;\n\tdefault:\n\t\tfprintf(stderr, \"internal error\\n\");\n\t\tabort();\n\t}\n}\n\nstatic inline void buf_init(struct buffer *buf, size_t size)\n{\n\tif (size) {\n\t\tbuf->p = (uint8_t *) malloc(size);\n\t\tif (!buf->p) {\n\t\t\tfprintf(stderr, \"sshfs: memory allocation failed\\n\");\n\t\t\tabort();\n\t\t}\n\t} else\n\t\tbuf->p = NULL;\n\tbuf->len = 0;\n\tbuf->size = size;\n}\n\nstatic inline void buf_free(struct buffer *buf)\n{\n\tfree(buf->p);\n}\n\nstatic inline void buf_finish(struct buffer *buf)\n{\n\tbuf->len = buf->size;\n}\n\nstatic inline void buf_clear(struct buffer *buf)\n{\n\tbuf_free(buf);\n\tbuf_init(buf, 0);\n}\n\nstatic void buf_resize(struct buffer *buf, size_t len)\n{\n\tbuf->size = (buf->len + len + 63) & ~31;\n\tbuf->p = (uint8_t *) realloc(buf->p, buf->size);\n\tif (!buf->p) {\n\t\tfprintf(stderr, \"sshfs: memory allocation failed\\n\");\n\t\tabort();\n\t}\n}\n\nstatic inline void buf_check_add(struct buffer *buf, size_t len)\n{\n\tif (buf->len + len > buf->size)\n\t\tbuf_resize(buf, len);\n}\n\n#define _buf_add_mem(b, d, l)\t\t\t\\\n\tbuf_check_add(b, l);\t\t\t\\\n\tmemcpy(b->p + b->len, d, l);\t\t\\\n\tb->len += l;\n\n\nstatic inline void buf_add_mem(struct buffer *buf, const void *data,\n                               size_t len)\n{\n\t_buf_add_mem(buf, data, len);\n}\n\nstatic inline void buf_add_buf(struct buffer *buf, const struct buffer *bufa)\n{\n\t_buf_add_mem(buf, bufa->p, bufa->len);\n}\n\nstatic inline void buf_add_uint8(struct buffer *buf, uint8_t val)\n{\n\t_buf_add_mem(buf, &val, 1);\n}\n\nstatic inline void buf_add_uint32(struct buffer *buf, uint32_t val)\n{\n\tuint32_t nval = htonl(val);\n\t_buf_add_mem(buf, &nval, 4);\n}\n\nstatic inline void buf_add_uint64(struct buffer *buf, uint64_t val)\n{\n\tbuf_add_uint32(buf, val >> 32);\n\tbuf_add_uint32(buf, val & 0xffffffff);\n}\n\nstatic inline void buf_add_data(struct buffer *buf, const struct buffer *data)\n{\n\tbuf_add_uint32(buf, data->len);\n\tbuf_add_mem(buf, data->p, data->len);\n}\n\nstatic inline void buf_add_string(struct buffer *buf, const char *str)\n{\n\tstruct buffer data;\n\tdata.p = (uint8_t *) str;\n\tdata.len = strlen(str);\n\tbuf_add_data(buf, &data);\n}\n\nstatic inline void buf_add_path(struct buffer *buf, const char *path)\n{\n\tchar *realpath;\n\n\tif (sshfs.base_path[0]) {\n\t\tif (path[1]) {\n\t\t\tif (sshfs.base_path[strlen(sshfs.base_path)-1] != '/') {\n\t\t\t\trealpath = g_strdup_printf(\"%s/%s\",\n\t\t\t\t\t\t\t   sshfs.base_path,\n\t\t\t\t\t\t\t   path + 1);\n\t\t\t} else {\n\t\t\t\trealpath = g_strdup_printf(\"%s%s\",\n\t\t\t\t\t\t\t   sshfs.base_path,\n\t\t\t\t\t\t\t   path + 1);\n\t\t\t}\n\t\t} else {\n\t\t\trealpath = g_strdup(sshfs.base_path);\n\t\t}\n\t} else {\n\t\tif (path[1])\n\t\t\trealpath = g_strdup(path + 1);\n\t\telse\n\t\t\trealpath = g_strdup(\".\");\n\t}\n\tbuf_add_string(buf, realpath);\n\tg_free(realpath);\n}\n\nstatic int buf_check_get(struct buffer *buf, size_t len)\n{\n\tif (buf->len + len > buf->size) {\n\t\tfprintf(stderr, \"buffer too short\\n\");\n\t\treturn -1;\n\t} else\n\t\treturn 0;\n}\n\nstatic inline int buf_get_mem(struct buffer *buf, void *data, size_t len)\n{\n\tif (buf_check_get(buf, len) == -1)\n\t\treturn -1;\n\tmemcpy(data, buf->p + buf->len, len);\n\tbuf->len += len;\n\treturn 0;\n}\n\nstatic inline int buf_get_uint8(struct buffer *buf, uint8_t *val)\n{\n\treturn buf_get_mem(buf, val, 1);\n}\n\nstatic inline int buf_get_uint32(struct buffer *buf, uint32_t *val)\n{\n\tuint32_t nval;\n\tif (buf_get_mem(buf, &nval, 4) == -1)\n\t\treturn -1;\n\t*val = ntohl(nval);\n\treturn 0;\n}\n\nstatic inline int buf_get_uint64(struct buffer *buf, uint64_t *val)\n{\n\tuint32_t val1;\n\tuint32_t val2;\n\tif (buf_get_uint32(buf, &val1) == -1 ||\n\t    buf_get_uint32(buf, &val2) == -1) {\n\t\treturn -1;\n\t}\n\t*val = ((uint64_t) val1 << 32) + val2;\n\treturn 0;\n}\n\nstatic inline int buf_get_data(struct buffer *buf, struct buffer *data)\n{\n\tuint32_t len;\n\tif (buf_get_uint32(buf, &len) == -1 || len > buf->size - buf->len)\n\t\treturn -1;\n\tbuf_init(data, len + 1);\n\tdata->size = len;\n\tif (buf_get_mem(buf, data->p, data->size) == -1) {\n\t\tbuf_free(data);\n\t\treturn -1;\n\t}\n\treturn 0;\n}\n\nstatic inline int buf_get_string(struct buffer *buf, char **str)\n{\n\tstruct buffer data;\n\tif (buf_get_data(buf, &data) == -1)\n\t\treturn -1;\n\tdata.p[data.size] = '\\0';\n\t*str = (char *) data.p;\n\treturn 0;\n}\n\nstatic int buf_get_attrs(struct buffer *buf, struct stat *stbuf, int *flagsp)\n{\n\tuint32_t flags;\n\tuint64_t size = 0;\n\tuint32_t uid = 0;\n\tuint32_t gid = 0;\n\tuint32_t atime = 0;\n\tuint32_t mtime = 0;\n\tuint32_t mode = S_IFREG | 0777;\n\n\tif (buf_get_uint32(buf, &flags) == -1)\n\t\treturn -EIO;\n\tif (flagsp)\n\t\t*flagsp = flags;\n\tif ((flags & SSH_FILEXFER_ATTR_SIZE) &&\n\t    buf_get_uint64(buf, &size) == -1)\n\t\treturn -EIO;\n\tif ((flags & SSH_FILEXFER_ATTR_UIDGID) &&\n\t    (buf_get_uint32(buf, &uid) == -1 ||\n\t     buf_get_uint32(buf, &gid) == -1))\n\t\treturn -EIO;\n\tif ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&\n\t    buf_get_uint32(buf, &mode) == -1)\n\t\treturn -EIO;\n\tif ((flags & SSH_FILEXFER_ATTR_ACMODTIME)) {\n\t\tif (buf_get_uint32(buf, &atime) == -1 ||\n\t\t    buf_get_uint32(buf, &mtime) == -1)\n\t\t\treturn -EIO;\n\t}\n\tif ((flags & SSH_FILEXFER_ATTR_EXTENDED)) {\n\t\tuint32_t extcount;\n\t\tunsigned i;\n\t\tif (buf_get_uint32(buf, &extcount) == -1)\n\t\t\treturn -EIO;\n\t\tfor (i = 0; i < extcount; i++) {\n\t\t\tstruct buffer tmp;\n\t\t\tif (buf_get_data(buf, &tmp) == -1)\n\t\t\t\treturn -EIO;\n\t\t\tbuf_free(&tmp);\n\t\t\tif (buf_get_data(buf, &tmp) == -1)\n\t\t\t\treturn -EIO;\n\t\t\tbuf_free(&tmp);\n\t\t}\n\t}\n\n\tif (sshfs.remote_uid_detected) {\n\t\tif (uid == sshfs.remote_uid)\n\t\t\tuid = sshfs.local_uid;\n\t\tif (gid == sshfs.remote_gid)\n\t\t\tgid = sshfs.local_gid;\n\t}\n\tif (sshfs.idmap == IDMAP_FILE && sshfs.uid_map)\n\t\tif (translate_id(&uid, sshfs.uid_map) == -1)\n\t\t\treturn -EPERM;\n\tif (sshfs.idmap == IDMAP_FILE && sshfs.gid_map)\n\t\tif (translate_id(&gid, sshfs.gid_map) == -1)\n\t\t\treturn -EPERM;\n\n\tmemset(stbuf, 0, sizeof(struct stat));\n\tstbuf->st_mode = mode;\n\tstbuf->st_nlink = 1;\n\tstbuf->st_size = size;\n\tif (sshfs.blksize) {\n\t\tstbuf->st_blksize = sshfs.blksize;\n\t\tstbuf->st_blocks = ((size + sshfs.blksize - 1) &\n\t\t\t~((unsigned long long) sshfs.blksize - 1)) >> 9;\n\t}\n\tstbuf->st_uid = uid;\n\tstbuf->st_gid = gid;\n\tstbuf->st_atime = atime;\n\tstbuf->st_ctime = stbuf->st_mtime = mtime;\n\treturn 0;\n}\n\nstatic int buf_get_statvfs(struct buffer *buf, struct statvfs *stbuf)\n{\n\tuint64_t bsize;\n\tuint64_t frsize;\n\tuint64_t blocks;\n\tuint64_t bfree;\n\tuint64_t bavail;\n\tuint64_t files;\n\tuint64_t ffree;\n\tuint64_t favail;\n\tuint64_t fsid;\n\tuint64_t flag;\n\tuint64_t namemax;\n\n\tif (buf_get_uint64(buf, &bsize) == -1 ||\n\t    buf_get_uint64(buf, &frsize) == -1 ||\n\t    buf_get_uint64(buf, &blocks) == -1 ||\n\t    buf_get_uint64(buf, &bfree) == -1 ||\n\t    buf_get_uint64(buf, &bavail) == -1 ||\n\t    buf_get_uint64(buf, &files) == -1 ||\n\t    buf_get_uint64(buf, &ffree) == -1 ||\n\t    buf_get_uint64(buf, &favail) == -1 ||\n\t    buf_get_uint64(buf, &fsid) == -1 ||\n\t    buf_get_uint64(buf, &flag) == -1 ||\n\t    buf_get_uint64(buf, &namemax) == -1) {\n\t\treturn -1;\n\t}\n\n\tmemset(stbuf, 0, sizeof(struct statvfs));\n\tstbuf->f_bsize = bsize;\n\tstbuf->f_frsize = frsize;\n\tstbuf->f_blocks = blocks;\n\tstbuf->f_bfree = bfree;\n\tstbuf->f_bavail = bavail;\n\tstbuf->f_files = files;\n\tstbuf->f_ffree = ffree;\n\tstbuf->f_favail = favail;\n\tstbuf->f_namemax = namemax;\n\n\treturn 0;\n}\n\nstatic int buf_get_entries(struct buffer *buf, void *dbuf,\n                           fuse_fill_dir_t filler)\n{\n\tuint32_t count;\n\tunsigned i;\n\n\tif (buf_get_uint32(buf, &count) == -1)\n\t\treturn -EIO;\n\n\tfor (i = 0; i < count; i++) {\n\t\tint err = -1;\n\t\tchar *name;\n\t\tchar *longname;\n\t\tstruct stat stbuf;\n\t\tif (buf_get_string(buf, &name) == -1)\n\t\t\treturn -EIO;\n\t\tif (buf_get_string(buf, &longname) != -1) {\n\t\t\tfree(longname);\n\t\t\terr = buf_get_attrs(buf, &stbuf, NULL);\n\t\t\tif (!err) {\n\t\t\t\tif (sshfs.follow_symlinks &&\n\t\t\t\t    S_ISLNK(stbuf.st_mode)) {\n\t\t\t\t\tstbuf.st_mode = 0;\n\t\t\t\t}\n\t\t\t\tfiller(dbuf, name, &stbuf, 0, 0);\n\t\t\t}\n\t\t}\n\t\tfree(name);\n\t\tif (err)\n\t\t\treturn err;\n\t}\n\treturn 0;\n}\n\nstatic void ssh_add_arg(const char *arg)\n{\n\tif (fuse_opt_add_arg(&sshfs.ssh_args, arg) == -1)\n\t\t_exit(1);\n}\n\n\nstatic int pty_expect_loop(struct conn *conn)\n{\n\tint res;\n\tchar buf[256];\n\tconst char *passwd_str = \"assword:\";\n\tint timeout = 60 * 1000; /* 1min timeout for the prompt to appear */\n\tint passwd_len = strlen(passwd_str);\n\tint len = 0;\n\tchar c;\n\n\twhile (1) {\n\t\tstruct pollfd fds[2];\n\n\t\tfds[0].fd = conn->rfd;\n\t\tfds[0].events = POLLIN;\n\t\tfds[1].fd = sshfs.ptyfd;\n\t\tfds[1].events = POLLIN;\n\t\tres = poll(fds, 2, timeout);\n\t\tif (res == -1) {\n\t\t\tperror(\"poll\");\n\t\t\treturn -1;\n\t\t}\n\t\tif (res == 0) {\n\t\t\tfprintf(stderr, \"Timeout waiting for prompt\\n\");\n\t\t\treturn -1;\n\t\t}\n\t\tif (fds[0].revents) {\n\t\t\t/*\n\t\t\t * Something happened on stdout of ssh, this\n\t\t\t * either means, that we are connected, or\n\t\t\t * that we are disconnected.  In any case the\n\t\t\t * password doesn't matter any more.\n\t\t\t */\n\t\t\tbreak;\n\t\t}\n\n\t\tres = read(sshfs.ptyfd, &c, 1);\n\t\tif (res == -1) {\n\t\t\tperror(\"read\");\n\t\t\treturn -1;\n\t\t}\n\t\tif (res == 0) {\n\t\t\tfprintf(stderr, \"EOF while waiting for prompt\\n\");\n\t\t\treturn -1;\n\t\t}\n\t\tbuf[len] = c;\n\t\tlen++;\n\t\tif (len == passwd_len) {\n\t\t\tif (memcmp(buf, passwd_str, passwd_len) == 0) {\n\t\t\t\twrite(sshfs.ptyfd, sshfs.password,\n\t\t\t\t      strlen(sshfs.password));\n\t\t\t}\n\t\t\tmemmove(buf, buf + 1, passwd_len - 1);\n\t\t\tlen--;\n\t\t}\n\t}\n\n\tif (!sshfs.reconnect) {\n\t\tsize_t size = getpagesize();\n\n\t\tmemset(sshfs.password, 0, size);\n\t\tmunmap(sshfs.password, size);\n\t\tsshfs.password = NULL;\n\t}\n\n\treturn 0;\n}\n\nstatic struct conn* get_conn(const struct sshfs_file *sf,\n\t\t\t     const char *path)\n{\n\tstruct conntab_entry *ce;\n\tint i;\n\n\tif (sshfs.max_conns == 1)\n\t\treturn &sshfs.conns[0];\n\n\tif (sf != NULL)\n\t\treturn sf->conn;\n\n\tif (path != NULL) {\n\t\tpthread_mutex_lock(&sshfs.lock);\n\t\tce = g_hash_table_lookup(sshfs.conntab, path);\n\n\t\tif (ce != NULL) {\n\t\t\tstruct conn *conn = ce->conn;\n\t\t\tpthread_mutex_unlock(&sshfs.lock);\n\t\t\treturn conn;\n\t\t}\n\t\tpthread_mutex_unlock(&sshfs.lock);\n\t}\n\n\tint best_index = 0;\n\tuint64_t best_score = ~0ULL; /* smaller is better */\n\tfor (i = 0; i < sshfs.max_conns; i++) {\n\t\tuint64_t score = ((uint64_t) sshfs.conns[i].req_count << 43) +\n\t\t\t\t ((uint64_t) sshfs.conns[i].dir_count << 22) +\n\t\t\t\t ((uint64_t) sshfs.conns[i].file_count << 1) +\n\t\t\t\t (uint64_t) (sshfs.conns[i].rfd >= 0 ? 0 : 1);\n\t\tif (score < best_score) {\n\t\t\tbest_index = i;\n\t\t\tbest_score = score;\n\t\t}\n\t}\n\treturn &sshfs.conns[best_index];\n}\n\nstatic int pty_master(char **name)\n{\n\tint mfd;\n\n\tmfd = open(\"/dev/ptmx\", O_RDWR | O_NOCTTY);\n\tif (mfd == -1) {\n\t\tperror(\"failed to open pty\");\n\t\treturn -1;\n\t}\n\tif (grantpt(mfd) != 0) {\n\t\tperror(\"grantpt\");\n\t\treturn -1;\n\t}\n\tif (unlockpt(mfd) != 0) {\n\t\tperror(\"unlockpt\");\n\t\treturn -1;\n\t}\n\t*name = ptsname(mfd);\n\n\treturn mfd;\n}\n\nstatic void replace_arg(char **argp, const char *newarg)\n{\n\tfree(*argp);\n\t*argp = strdup(newarg);\n\tif (*argp == NULL) {\n\t\tfprintf(stderr, \"sshfs: memory allocation failed\\n\");\n\t\tabort();\n\t}\n}\n\nstatic int start_ssh(struct conn *conn)\n{\n\tchar *ptyname = NULL;\n\tint sockpair[2];\n\tint pid;\n\n\tif (sshfs.password_stdin) {\n\n\t\tsshfs.ptyfd = pty_master(&ptyname);\n\t\tif (sshfs.ptyfd == -1)\n\t\t\treturn -1;\n\n\t\tsshfs.ptypassivefd = open(ptyname, O_RDWR | O_NOCTTY);\n\t\tif (sshfs.ptypassivefd == -1)\n\t\t\treturn -1;\n\t}\n\n\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) == -1) {\n\t\tperror(\"failed to create socket pair\");\n\t\treturn -1;\n\t}\n\tconn->rfd = sockpair[0];\n\tconn->wfd = sockpair[0];\n\n\tpid = fork();\n\tif (pid == -1) {\n\t\tperror(\"failed to fork\");\n\t\tclose(sockpair[1]);\n\t\treturn -1;\n\t} else if (pid == 0) {\n\t\tint devnull;\n\n\t\tdevnull = open(\"/dev/null\", O_WRONLY);\n\n\t\tif (dup2(sockpair[1], 0) == -1 || dup2(sockpair[1], 1) == -1) {\n\t\t\tperror(\"failed to redirect input/output\");\n\t\t\t_exit(1);\n\t\t}\n\t\tif (!sshfs.verbose && !sshfs.foreground && devnull != -1)\n\t\t\tdup2(devnull, 2);\n\n\t\tclose(devnull);\n\t\tclose(sockpair[0]);\n\t\tclose(sockpair[1]);\n\n\t\tswitch (fork()) {\n\t\tcase -1:\n\t\t\tperror(\"failed to fork\");\n\t\t\t_exit(1);\n\t\tcase 0:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t_exit(0);\n\t\t}\n\t\tchdir(\"/\");\n\t\t/*\n\t\t * Avoid processes hanging trying to stat() OLDPWD if it is in\n\t\t * the mount point. This can be removed if sshfs opens the\n\t\t * mount point after establishing the ssh connection.\n\t\t */\n\t\tunsetenv(\"OLDPWD\");\n\n\t\tif (sshfs.password_stdin) {\n\t\t\tint sfd;\n\n\t\t\tsetsid();\n\t\t\tsfd = open(ptyname, O_RDWR);\n\t\t\tif (sfd == -1) {\n\t\t\t\tperror(ptyname);\n\t\t\t\t_exit(1);\n\t\t\t}\n\t\t\tclose(sfd);\n\t\t\tclose(sshfs.ptypassivefd);\n\t\t\tclose(sshfs.ptyfd);\n\t\t}\n\n\t\tif (sshfs.debug) {\n\t\t\tint i;\n\n\t\t\tfprintf(stderr, \"executing\");\n\t\t\tfor (i = 0; i < sshfs.ssh_args.argc; i++)\n\t\t\t\tfprintf(stderr, \" <%s>\",\n\t\t\t\t\tsshfs.ssh_args.argv[i]);\n\t\t\tfprintf(stderr, \"\\n\");\n\t\t}\n\n#if defined(__CYGWIN__)\n\t\t/* \n\t\t * Windows native OpenSSH stdio behavior. For details check\n\t\t * https://github.com/PowerShell/openssh-portable/pull/759\n\t\t */\n\t\tputenv(\"OPENSSH_STDIO_MODE=nonsock\");\n#endif\n\t\texecvp(sshfs.ssh_args.argv[0], sshfs.ssh_args.argv);\n\t\tfprintf(stderr, \"failed to execute '%s': %s\\n\",\n\t\t\tsshfs.ssh_args.argv[0], strerror(errno));\n\t\t_exit(1);\n\t}\n\twaitpid(pid, NULL, 0);\n\tclose(sockpair[1]);\n\treturn 0;\n}\n\nstatic int connect_passive(struct conn *conn)\n{\n\tconn->rfd = STDIN_FILENO;\n\tconn->wfd = STDOUT_FILENO;\n\treturn 0;\n}\n\nstatic int connect_to(struct conn *conn, char *host, char *port)\n{\n\tint err;\n\tint sock;\n\tint opt;\n\tstruct addrinfo *ai;\n\tstruct addrinfo hint;\n\n\tmemset(&hint, 0, sizeof(hint));\n\thint.ai_family = PF_INET;\n\tif (strstr(host, \":\") != NULL) { // only ipv6 should have : in it, normal IP and domains do not.\n\t\thint.ai_family = PF_INET6;\n\t\tDEBUG(\"using ipv6 to connect to host %s\\n\", host);\n\t}\n\thint.ai_socktype = SOCK_STREAM;\n\terr = getaddrinfo(host, port, &hint, &ai);\n\tif (err) {\n\t\tfprintf(stderr, \"failed to resolve %s:%s: %s\\n\", host, port,\n\t\t\tgai_strerror(err));\n\t\treturn -1;\n\t}\n\tsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);\n\tif (sock == -1) {\n\t\tperror(\"failed to create socket\");\n\t\tfreeaddrinfo(ai);\n\t\treturn -1;\n\t}\n\terr = connect(sock, ai->ai_addr, ai->ai_addrlen);\n\tif (err == -1) {\n\t\tperror(\"failed to connect\");\n\t\tfreeaddrinfo(ai);\n\t\tclose(sock);\n\t\treturn -1;\n\t}\n\topt = 1;\n\terr = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));\n\tif (err == -1)\n\t\tperror(\"warning: failed to set TCP_NODELAY\");\n\n\tfreeaddrinfo(ai);\n\n\tconn->rfd = sock;\n\tconn->wfd = sock;\n\treturn 0;\n}\n\nstatic int connect_vsock(struct conn *conn, char *vsock)\n{\n#ifndef __linux__\n\tfprintf(stderr, \"vsock is not available\\n\");\n\treturn -1;\n#else\n\tint err;\n\tint sock;\n\tstruct sockaddr_vm addr;\n\tunsigned int cid;\n\tunsigned int port;\n\tchar *delim;\n\n\tdelim = strchr(vsock, ':');\n\tif (delim == NULL) {\n\t\tfprintf(stderr, \"invalid vsock, expecting CID:PORT\\n\");\n\t\treturn -1;\n\t}\n\t*delim = '\\0';\n\terrno = 0;\n\tcid = strtoul(vsock, NULL, 10);\n\tif (errno) {\n\t\tperror(\"invalid cid\");\n\t\treturn -1;\n\t}\n\terrno = 0;\n\tport = strtoul(delim + 1, NULL, 10);\n\tif (errno) {\n\t\tperror(\"invalid port\");\n\t\treturn -1;\n\t}\n\n\tsock = socket(AF_VSOCK, SOCK_STREAM, 0);\n\tif (sock == -1) {\n\t\tperror(\"failed to create socket\");\n\t\treturn -1;\n\t}\n\tmemset(&addr, 0, sizeof(addr));\n\taddr.svm_family = AF_VSOCK;\n\taddr.svm_cid = cid;\n\taddr.svm_port = port;\n\terr = connect(sock, (const struct sockaddr *)&addr, sizeof(addr));\n\tif (err == -1) {\n\t\tperror(\"failed to connect vsock\");\n\t\tclose(sock);\n\t\treturn -1;\n\t}\n\n\tconn->rfd = sock;\n\tconn->wfd = sock;\n\treturn 0;\n#endif\n}\n\nstatic int do_write(struct conn *conn, struct iovec *iov, size_t count)\n{\n\tint res;\n\twhile (count) {\n\t\tres = writev(conn->wfd, iov, count);\n\t\tif (res == -1) {\n\t\t\tperror(\"write\");\n\t\t\treturn -1;\n\t\t} else if (res == 0) {\n\t\t\tfprintf(stderr, \"zero write\\n\");\n\t\t\treturn -1;\n\t\t}\n\t\tdo {\n\t\t\tif ((unsigned) res < iov->iov_len) {\n\t\t\t\tiov->iov_len -= res;\n\t\t\t\tiov->iov_base += res;\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t\tres -= iov->iov_len;\n\t\t\t\tcount --;\n\t\t\t\tiov ++;\n\t\t\t}\n\t\t} while(count);\n\t}\n\treturn 0;\n}\n\nstatic uint32_t sftp_get_id(void)\n{\n\tstatic uint32_t idctr;\n\treturn idctr++;\n}\n\nstatic void buf_to_iov(const struct buffer *buf, struct iovec *iov)\n{\n\tiov->iov_base = buf->p;\n\tiov->iov_len = buf->len;\n}\n\nstatic size_t iov_length(const struct iovec *iov, unsigned long nr_segs)\n{\n\tunsigned long seg;\n\tsize_t ret = 0;\n\n\tfor (seg = 0; seg < nr_segs; seg++)\n\t\tret += iov[seg].iov_len;\n\treturn ret;\n}\n\n#define SFTP_MAX_IOV 3\n\nstatic int sftp_send_iov(struct conn *conn, uint8_t type, uint32_t id,\n                         struct iovec iov[], size_t count)\n{\n\tint res;\n\tstruct buffer buf;\n\tstruct iovec iovout[SFTP_MAX_IOV];\n\tunsigned i;\n\tunsigned nout = 0;\n\n\tassert(count <= SFTP_MAX_IOV - 1);\n\tbuf_init(&buf, 9);\n\tbuf_add_uint32(&buf, iov_length(iov, count) + 5);\n\tbuf_add_uint8(&buf, type);\n\tbuf_add_uint32(&buf, id);\n\tbuf_to_iov(&buf, &iovout[nout++]);\n\tfor (i = 0; i < count; i++)\n\t\tiovout[nout++] = iov[i];\n\tpthread_mutex_lock(&conn->lock_write);\n\tres = do_write(conn, iovout, nout);\n\tpthread_mutex_unlock(&conn->lock_write);\n\tbuf_free(&buf);\n\treturn res;\n}\n\nstatic int do_read(struct conn *conn, struct buffer *buf)\n{\n\tint res;\n\tuint8_t *p = buf->p;\n\tsize_t size = buf->size;\n\twhile (size) {\n\t\tres = read(conn->rfd, p, size);\n\t\tif (res == -1) {\n\t\t\tperror(\"read\");\n\t\t\treturn -1;\n\t\t} else if (res == 0) {\n\t\t\tfprintf(stderr, \"remote host has disconnected\\n\");\n\t\t\treturn -1;\n\t\t}\n\t\tsize -= res;\n\t\tp += res;\n\t}\n\treturn 0;\n}\n\nstatic int sftp_read(struct conn *conn, uint8_t *type, struct buffer *buf)\n{\n\tint res;\n\tstruct buffer buf2;\n\tuint32_t len;\n\tbuf_init(&buf2, 5);\n\tres = do_read(conn, &buf2);\n\tif (res != -1) {\n\t\tif (buf_get_uint32(&buf2, &len) == -1)\n\t\t\treturn -1;\n\t\tif (len > MAX_REPLY_LEN) {\n\t\t\tfprintf(stderr, \"reply len too large: %u\\n\", len);\n\t\t\treturn -1;\n\t\t}\n\t\tif (buf_get_uint8(&buf2, type) == -1)\n\t\t\treturn -1;\n\t\tbuf_init(buf, len - 1);\n\t\tres = do_read(conn, buf);\n\t}\n\tbuf_free(&buf2);\n\treturn res;\n}\n\nstatic void request_free(struct request *req)\n{\n\tif (req->end_func)\n\t\treq->end_func(req);\n\n\treq->conn->req_count--;\n\n\tbuf_free(&req->reply);\n\tsem_destroy(&req->ready);\n\tg_free(req);\n}\n\nstatic void chunk_free(struct read_chunk *chunk)\n{\n\twhile (!list_empty(&chunk->reqs)) {\n\t\tstruct read_req *rreq;\n\n\t\trreq = list_entry(chunk->reqs.prev, struct read_req, list);\n\t\tlist_del(&rreq->list);\n\t\tbuf_free(&rreq->data);\n\t\tg_free(rreq);\n\t}\n\tg_free(chunk);\n}\n\nstatic void chunk_put(struct read_chunk *chunk)\n{\n\tif (chunk) {\n\t\tchunk->refs--;\n\t\tif (!chunk->refs)\n\t\t\tchunk_free(chunk);\n\t}\n}\n\nstatic void chunk_put_locked(struct read_chunk *chunk)\n{\n\tpthread_mutex_lock(&sshfs.lock);\n\tchunk_put(chunk);\n\tpthread_mutex_unlock(&sshfs.lock);\n}\n\nstatic int clean_req(void *key, struct request *req, gpointer user_data)\n{\n\t(void) key;\n\tstruct conn* conn = (struct conn*) user_data;\n\n\tif (req->conn != conn)\n\t\treturn FALSE;\n\n\treq->error = -EIO;\n\tif (req->want_reply)\n\t\tsem_post(&req->ready);\n\telse\n\t\trequest_free(req);\n\n\treturn TRUE;\n}\n\nstatic int process_one_request(struct conn *conn)\n{\n\tint res;\n\tstruct buffer buf;\n\tuint8_t type;\n\tstruct request *req;\n\tuint32_t id;\n\n\tbuf_init(&buf, 0);\n\tres = sftp_read(conn, &type, &buf);\n\tif (res == -1)\n\t\treturn -1;\n\tif (buf_get_uint32(&buf, &id) == -1)\n\t\treturn -1;\n\n\tpthread_mutex_lock(&sshfs.lock);\n\treq = (struct request *)\n\t\tg_hash_table_lookup(sshfs.reqtab, GUINT_TO_POINTER(id));\n\tif (req == NULL)\n\t\tfprintf(stderr, \"request %i not found\\n\", id);\n\telse {\n\t\tint was_over;\n\n\t\twas_over = sshfs.outstanding_len > sshfs.max_outstanding_len;\n\t\tsshfs.outstanding_len -= req->len;\n\t\tif (was_over &&\n\t\t    sshfs.outstanding_len <= sshfs.max_outstanding_len) {\n\t\t\tpthread_cond_broadcast(&sshfs.outstanding_cond);\n\t\t}\n\t\tg_hash_table_remove(sshfs.reqtab, GUINT_TO_POINTER(id));\n\t}\n\tpthread_mutex_unlock(&sshfs.lock);\n\tif (req != NULL) {\n\t\tif (sshfs.debug) {\n\t\t\tstruct timeval now;\n\t\t\tunsigned int difftime;\n\t\t\tunsigned msgsize = buf.size + 5;\n\n\t\t\tgettimeofday(&now, NULL);\n\t\t\tdifftime = (now.tv_sec - req->start.tv_sec) * 1000;\n\t\t\tdifftime += (now.tv_usec - req->start.tv_usec) / 1000;\n\t\t\tDEBUG(\"  [%05i] %14s %8ubytes (%ims)\\n\", id,\n\t\t\t      type_name(type), msgsize, difftime);\n\n\t\t\tif (difftime < sshfs.min_rtt || !sshfs.num_received)\n\t\t\t\tsshfs.min_rtt = difftime;\n\t\t\tif (difftime > sshfs.max_rtt)\n\t\t\t\tsshfs.max_rtt = difftime;\n\t\t\tsshfs.total_rtt += difftime;\n\t\t\tsshfs.num_received++;\n\t\t\tsshfs.bytes_received += msgsize;\n\t\t}\n\t\treq->reply = buf;\n\t\treq->reply_type = type;\n\t\treq->replied = 1;\n\t\tif (req->want_reply)\n\t\t\tsem_post(&req->ready);\n\t\telse {\n\t\t\tpthread_mutex_lock(&sshfs.lock);\n\t\t\trequest_free(req);\n\t\t\tpthread_mutex_unlock(&sshfs.lock);\n\t\t}\n\t} else\n\t\tbuf_free(&buf);\n\n\treturn 0;\n}\n\nstatic void close_conn(struct conn *conn)\n{\n\tclose(conn->rfd);\n\tif (conn->rfd != conn->wfd)\n\t\tclose(conn->wfd);\n\tconn->rfd = -1;\n\tconn->wfd = -1;\n\tif (sshfs.ptyfd != -1) {\n\t\tclose(sshfs.ptyfd);\n\t\tsshfs.ptyfd = -1;\n\t}\n\tif (sshfs.ptypassivefd != -1) {\n\t\tclose(sshfs.ptypassivefd);\n\t\tsshfs.ptypassivefd = -1;\n\t}\n}\n\nstatic void *process_requests(void *data_)\n{\n\t(void) data_;\n\tstruct conn *conn = data_;\n\n\twhile (1) {\n\t\tif (process_one_request(conn) == -1)\n\t\t\tbreak;\n\t}\n\n\tpthread_mutex_lock(&sshfs.lock);\n\tconn->processing_thread_started = 0;\n\tclose_conn(conn);\n\tg_hash_table_foreach_remove(sshfs.reqtab, (GHRFunc) clean_req, conn);\n\tconn->connver = ++sshfs.connvers;\n\tsshfs.outstanding_len = 0;\n\tpthread_cond_broadcast(&sshfs.outstanding_cond);\n\tpthread_mutex_unlock(&sshfs.lock);\n\n\tif (!sshfs.reconnect) {\n\t\t/* harakiri */\n\t\tkill(getpid(), SIGTERM);\n\t}\n\treturn NULL;\n}\n\nstatic int sftp_init_reply_ok(struct conn *conn, struct buffer *buf,\n                              uint32_t *version)\n{\n\tuint32_t len;\n\tuint8_t type;\n\n\tif (buf_get_uint32(buf, &len) == -1)\n\t\treturn -1;\n\n\tif (len < 5 || len > MAX_REPLY_LEN)\n\t\treturn 1;\n\n\tif (buf_get_uint8(buf, &type) == -1)\n\t\treturn -1;\n\n\tif (type != SSH_FXP_VERSION)\n\t\treturn 1;\n\n\tif (buf_get_uint32(buf, version) == -1)\n\t\treturn -1;\n\n\tDEBUG(\"Server version: %u\\n\", *version);\n\n\tif (len > 5) {\n\t\tstruct buffer buf2;\n\n\t\tbuf_init(&buf2, len - 5);\n\t\tif (do_read(conn, &buf2) == -1) {\n\t\t\tbuf_free(&buf2);\n\t\t\treturn -1;\n\t\t}\n\n\t\tdo {\n\t\t\tchar *ext = NULL;\n\t\t\tchar *extdata = NULL;\n\n\t\t\tif (buf_get_string(&buf2, &ext) == -1 ||\n\t\t\t    buf_get_string(&buf2, &extdata) == -1) {\n\t\t\t\tbuf_free(&buf2);\n\t\t\t\tfree(ext);\n\t\t\t\tfree(extdata);\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tDEBUG(\"Extension: %s <%s>\\n\", ext, extdata);\n\n\t\t\tif (strcmp(ext, SFTP_EXT_POSIX_RENAME) == 0 &&\n\t\t\t    strcmp(extdata, \"1\") == 0) {\n\t\t\t\tsshfs.ext_posix_rename = 1;\n\t\t\t\tsshfs.rename_workaround = 0;\n\t\t\t}\n\t\t\tif (strcmp(ext, SFTP_EXT_STATVFS) == 0 &&\n\t\t\t    strcmp(extdata, \"2\") == 0)\n\t\t\t\tsshfs.ext_statvfs = 1;\n\t\t\tif (strcmp(ext, SFTP_EXT_HARDLINK) == 0 &&\n\t\t\t    strcmp(extdata, \"1\") == 0)\n\t\t\t\tsshfs.ext_hardlink = 1;\n\t\t\tif (strcmp(ext, SFTP_EXT_FSYNC) == 0 &&\n\t\t\t    strcmp(extdata, \"1\") == 0)\n\t\t\t\tsshfs.ext_fsync = 1;\n\n\t\t\tfree(ext);\n\t\t\tfree(extdata);\n\t\t} while (buf2.len < buf2.size);\n\t\tbuf_free(&buf2);\n\t}\n\treturn 0;\n}\n\nstatic int sftp_find_init_reply(struct conn *conn, uint32_t *version)\n{\n\tint res;\n\tstruct buffer buf;\n\n\tbuf_init(&buf, 9);\n\tres = do_read(conn, &buf);\n\twhile (res != -1) {\n\t\tstruct buffer buf2;\n\n\t\tres = sftp_init_reply_ok(conn, &buf, version);\n\t\tif (res <= 0)\n\t\t\tbreak;\n\n\t\t/* Iterate over any rubbish until the version reply is found */\n\t\tDEBUG(\"%c\", *buf.p);\n\t\tmemmove(buf.p, buf.p + 1, buf.size - 1);\n\t\tbuf.len = 0;\n\t\tbuf2.p = buf.p + buf.size - 1;\n\t\tbuf2.size = 1;\n\t\tres = do_read(conn, &buf2);\n\t}\n\tbuf_free(&buf);\n\treturn res;\n}\n\nstatic int sftp_init(struct conn *conn)\n{\n\tint res = -1;\n\tuint32_t version = 0;\n\tstruct buffer buf;\n\tbuf_init(&buf, 0);\n\tif (sftp_send_iov(conn, SSH_FXP_INIT, PROTO_VERSION, NULL, 0) == -1)\n\t\tgoto out;\n\n\tif (sshfs.password_stdin && pty_expect_loop(conn) == -1)\n\t\tgoto out;\n\n\tif (sftp_find_init_reply(conn, &version) == -1)\n\t\tgoto out;\n\n\tsshfs.server_version = version;\n\tif (version > PROTO_VERSION) {\n\t\tfprintf(stderr,\n\t\t\t\"Warning: server uses version: %i, we support: %i\\n\",\n\t\t\tversion, PROTO_VERSION);\n\t}\n\tres = 0;\n\nout:\n\tbuf_free(&buf);\n\treturn res;\n}\n\nstatic int sftp_error_to_errno(uint32_t error)\n{\n\tswitch (error) {\n\tcase SSH_FX_OK:                return 0;\n\tcase SSH_FX_NO_SUCH_FILE:      return ENOENT;\n\tcase SSH_FX_PERMISSION_DENIED: return EACCES;\n\tcase SSH_FX_FAILURE:           return EPERM;\n\tcase SSH_FX_BAD_MESSAGE:       return EBADMSG;\n\tcase SSH_FX_NO_CONNECTION:     return ENOTCONN;\n\tcase SSH_FX_CONNECTION_LOST:   return ECONNABORTED;\n\tcase SSH_FX_OP_UNSUPPORTED:    return EOPNOTSUPP;\n\tdefault:                       return EIO;\n\t}\n}\n\nstatic void sftp_detect_uid(struct conn *conn)\n{\n\tint flags;\n\tuint32_t id = sftp_get_id();\n\tuint32_t replid;\n\tuint8_t type;\n\tstruct buffer buf;\n\tstruct stat stbuf;\n\tstruct iovec iov[1];\n\n\tbuf_init(&buf, 5);\n\tbuf_add_string(&buf, \".\");\n\tbuf_to_iov(&buf, &iov[0]);\n\tif (sftp_send_iov(conn, SSH_FXP_STAT, id, iov, 1) == -1)\n\t\tgoto out;\n\tbuf_clear(&buf);\n\tif (sftp_read(conn, &type, &buf) == -1)\n\t\tgoto out;\n\tif (type != SSH_FXP_ATTRS && type != SSH_FXP_STATUS) {\n\t\tfprintf(stderr, \"protocol error\\n\");\n\t\tgoto out;\n\t}\n\tif (buf_get_uint32(&buf, &replid) == -1)\n\t\tgoto out;\n\tif (replid != id) {\n\t\tfprintf(stderr, \"bad reply ID\\n\");\n\t\tgoto out;\n\t}\n\tif (type == SSH_FXP_STATUS) {\n\t\tuint32_t serr;\n\t\tif (buf_get_uint32(&buf, &serr) == -1)\n\t\t\tgoto out;\n\n\t\tfprintf(stderr, \"failed to stat home directory (%i)\\n\", serr);\n\t\tgoto out;\n\t}\n\tif (buf_get_attrs(&buf, &stbuf, &flags) != 0)\n\t\tgoto out;\n\n\tif (!(flags & SSH_FILEXFER_ATTR_UIDGID))\n\t\tgoto out;\n\n\tsshfs.remote_uid = stbuf.st_uid;\n\tsshfs.local_uid = getuid();\n\tsshfs.remote_gid = stbuf.st_gid;\n\tsshfs.local_gid = getgid();\n\tsshfs.remote_uid_detected = 1;\n\tDEBUG(\"remote_uid = %i\\n\", sshfs.remote_uid);\n\nout:\n\tif (!sshfs.remote_uid_detected)\n\t\tfprintf(stderr, \"failed to detect remote user ID\\n\");\n\n\tbuf_free(&buf);\n}\n\nstatic int sftp_check_root(struct conn *conn, const char *base_path)\n{\n\tint flags;\n\tuint32_t id = sftp_get_id();\n\tuint32_t replid;\n\tuint8_t type;\n\tstruct buffer buf;\n\tstruct stat stbuf;\n\tstruct iovec iov[1];\n\tint err = -1;\n\tconst char *remote_dir = base_path[0] ? base_path : \".\";\n\n\tbuf_init(&buf, 0);\n\tbuf_add_string(&buf, remote_dir);\n\tbuf_to_iov(&buf, &iov[0]);\n\tif (sftp_send_iov(conn, SSH_FXP_LSTAT, id, iov, 1) == -1)\n\t\tgoto out;\n\tbuf_clear(&buf);\n\tif (sftp_read(conn, &type, &buf) == -1)\n\t\tgoto out;\n\tif (type != SSH_FXP_ATTRS && type != SSH_FXP_STATUS) {\n\t\tfprintf(stderr, \"protocol error\\n\");\n\t\tgoto out;\n\t}\n\tif (buf_get_uint32(&buf, &replid) == -1)\n\t\tgoto out;\n\tif (replid != id) {\n\t\tfprintf(stderr, \"bad reply ID\\n\");\n\t\tgoto out;\n\t}\n\tif (type == SSH_FXP_STATUS) {\n\t\tuint32_t serr;\n\t\tif (buf_get_uint32(&buf, &serr) == -1)\n\t\t\tgoto out;\n\n\t\tfprintf(stderr, \"%s:%s: %s\\n\", sshfs.host, remote_dir,\n\t\t\tstrerror(sftp_error_to_errno(serr)));\n\n\t\tgoto out;\n\t}\n\n\tint err2 = buf_get_attrs(&buf, &stbuf, &flags);\n\tif (err2) {\n\t\terr = err2;\n\t\tgoto out;\n\t}\n\n\tif (!(flags & SSH_FILEXFER_ATTR_PERMISSIONS))\n\t\tgoto out;\n\n\tif (!S_ISDIR(stbuf.st_mode)) {\n\t\tfprintf(stderr, \"%s:%s: Not a directory\\n\", sshfs.host,\n\t\t\tremote_dir);\n\t\tgoto out;\n\t}\n\n\terr = 0;\n\nout:\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int connect_remote(struct conn *conn)\n{\n\tint err;\n\n\tif (sshfs.passive)\n\t\terr = connect_passive(conn);\n\telse if (sshfs.directport)\n\t\terr = connect_to(conn, sshfs.host, sshfs.directport);\n\telse if (sshfs.vsock)\n\t\terr = connect_vsock(conn, sshfs.vsock);\n\telse\n\t\terr = start_ssh(conn);\n\tif (!err)\n\t\terr = sftp_init(conn);\n\n\tif (err)\n\t\tclose_conn(conn);\n\telse\n\t\tsshfs.num_connect++;\n\n\treturn err;\n}\n\nstatic int start_processing_thread(struct conn *conn)\n{\n\tint err;\n\tpthread_t thread_id;\n\tsigset_t oldset;\n\tsigset_t newset;\n\n\tif (conn->processing_thread_started)\n\t\treturn 0;\n\n\tif (conn->rfd == -1) {\n\t\terr = connect_remote(conn);\n\t\tif (err)\n\t\t\treturn -EIO;\n\t}\n\n\tif (sshfs.detect_uid) {\n\t\tsftp_detect_uid(conn);\n\t\tsshfs.detect_uid = 0;\n\t}\n\n\tsigemptyset(&newset);\n\tsigaddset(&newset, SIGTERM);\n\tsigaddset(&newset, SIGINT);\n\tsigaddset(&newset, SIGHUP);\n\tsigaddset(&newset, SIGQUIT);\n\tpthread_sigmask(SIG_BLOCK, &newset, &oldset);\n\terr = pthread_create(&thread_id, NULL, process_requests, conn);\n\tif (err) {\n\t\tfprintf(stderr, \"failed to create thread: %s\\n\", strerror(err));\n\t\treturn -EIO;\n\t}\n\tpthread_detach(thread_id);\n\tpthread_sigmask(SIG_SETMASK, &oldset, NULL);\n\tconn->processing_thread_started = 1;\n\treturn 0;\n}\n\nstatic void *sshfs_init(struct fuse_conn_info *conn,\n                        struct fuse_config *cfg)\n{\n\t/* Readahead should be done by kernel or sshfs but not both */\n\tif (conn->capable & FUSE_CAP_ASYNC_READ)\n\t\tsshfs.sync_read = 1;\n\n\t// These workarounds require the \"path\" argument.\n\tcfg->nullpath_ok = !(sshfs.truncate_workaround || sshfs.fstat_workaround);\n\n\t// When using multiple connections, release() needs to know the path\n\tif (sshfs.max_conns > 1)\n\t\tcfg->nullpath_ok = 0;\n\n\t// Lookup of . and .. is supported\n\tconn->capable |= FUSE_CAP_EXPORT_SUPPORT;\n\n\tif (!sshfs.delay_connect)\n\t\tstart_processing_thread(&sshfs.conns[0]);\n\n\t// SFTP only supports 1-second time resolution\n\tconn->time_gran = 1000000000;\n\n\treturn NULL;\n}\n\nstatic int sftp_request_wait(struct request *req, uint8_t type,\n                             uint8_t expect_type, struct buffer *outbuf)\n{\n\tint err;\n\n\tif (req->error) {\n\t\terr = req->error;\n\t\tgoto out;\n\t}\n\twhile (sem_wait(&req->ready));\n\tif (req->error) {\n\t\terr = req->error;\n\t\tgoto out;\n\t}\n\terr = -EIO;\n\tif (req->reply_type != expect_type &&\n\t    req->reply_type != SSH_FXP_STATUS) {\n\t\tfprintf(stderr, \"protocol error\\n\");\n\t\tgoto out;\n\t}\n\tif (req->reply_type == SSH_FXP_STATUS) {\n\t\tuint32_t serr;\n\t\tif (buf_get_uint32(&req->reply, &serr) == -1)\n\t\t\tgoto out;\n\n\t\tswitch (serr) {\n\t\tcase SSH_FX_OK:\n\t\t\tif (expect_type == SSH_FXP_STATUS)\n\t\t\t\terr = 0;\n\t\t\telse\n\t\t\t\terr = -EIO;\n\t\t\tbreak;\n\n\t\tcase SSH_FX_EOF:\n\t\t\tif (type == SSH_FXP_READ || type == SSH_FXP_READDIR)\n\t\t\t\terr = MY_EOF;\n\t\t\telse\n\t\t\t\terr = -EIO;\n\t\t\tbreak;\n\n\t\tcase SSH_FX_FAILURE:\n\t\t\tif (type == SSH_FXP_RMDIR)\n\t\t\t\terr = -ENOTEMPTY;\n\t\t\telse\n\t\t\t\terr = -EPERM;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\terr = -sftp_error_to_errno(serr);\n\t\t}\n\t} else {\n\t\tbuf_init(outbuf, req->reply.size - req->reply.len);\n\t\tbuf_get_mem(&req->reply, outbuf->p, outbuf->size);\n\t\terr = 0;\n\t}\n\nout:\n\tpthread_mutex_lock(&sshfs.lock);\n\trequest_free(req);\n\tpthread_mutex_unlock(&sshfs.lock);\n\treturn err;\n}\n\nstatic int sftp_request_send(struct conn *conn, uint8_t type, struct iovec *iov,\n\t\t\t     size_t count, request_func begin_func, request_func end_func,\n\t\t\t     int want_reply, void *data, struct request **reqp)\n{\n\tint err;\n\tuint32_t id;\n\tstruct request *req = g_new0(struct request, 1);\n\n\treq->want_reply = want_reply;\n\treq->end_func = end_func;\n\treq->data = data;\n\tsem_init(&req->ready, 0, 0);\n\tbuf_init(&req->reply, 0);\n\tpthread_mutex_lock(&sshfs.lock);\n\tif (begin_func)\n\t\tbegin_func(req);\n\tid = sftp_get_id();\n\treq->id = id;\n\treq->conn = conn;\n\treq->conn->req_count++;\n\terr = start_processing_thread(conn);\n\tif (err) {\n\t\tpthread_mutex_unlock(&sshfs.lock);\n\t\tgoto out;\n\t}\n\treq->len = iov_length(iov, count) + 9;\n\tsshfs.outstanding_len += req->len;\n\twhile (sshfs.outstanding_len > sshfs.max_outstanding_len)\n\t\tpthread_cond_wait(&sshfs.outstanding_cond, &sshfs.lock);\n\n\tg_hash_table_insert(sshfs.reqtab, GUINT_TO_POINTER(id), req);\n\tif (sshfs.debug) {\n\t\tgettimeofday(&req->start, NULL);\n\t\tsshfs.num_sent++;\n\t\tsshfs.bytes_sent += req->len;\n\t}\n\tDEBUG(\"[%05i] %s\\n\", id, type_name(type));\n\tpthread_mutex_unlock(&sshfs.lock);\n\n\terr = -EIO;\n\tif (sftp_send_iov(conn, type, id, iov, count) == -1) {\n\t\tgboolean rmed;\n\n\t\tpthread_mutex_lock(&sshfs.lock);\n\t\trmed = g_hash_table_remove(sshfs.reqtab, GUINT_TO_POINTER(id));\n\t\tpthread_mutex_unlock(&sshfs.lock);\n\n\t\tif (!rmed && !want_reply) {\n\t\t\t/* request already freed */\n\t\t\treturn err;\n\t\t}\n\t\tgoto out;\n\t}\n\tif (want_reply)\n\t\t*reqp = req;\n\treturn 0;\n\nout:\n\treq->error = err;\n\tif (!want_reply)\n\t\tsftp_request_wait(req, type, 0, NULL);\n\telse\n\t\t*reqp = req;\n\n\treturn err;\n}\n\nstatic int sftp_request_iov(struct conn *conn, uint8_t type, struct iovec *iov,\n\t\t\t    size_t count, uint8_t expect_type, struct buffer *outbuf)\n{\n\tint err;\n\tstruct request *req;\n\n\terr = sftp_request_send(conn, type, iov, count, NULL, NULL,\n\t\t\t\texpect_type, NULL, &req);\n\tif (expect_type == 0)\n\t\treturn err;\n\n\treturn sftp_request_wait(req, type, expect_type, outbuf);\n}\n\nstatic int sftp_request(struct conn *conn, uint8_t type, const struct buffer *buf,\n\t\t\tuint8_t expect_type, struct buffer *outbuf)\n{\n\tstruct iovec iov;\n\n\tbuf_to_iov(buf, &iov);\n\treturn sftp_request_iov(conn, type, &iov, 1, expect_type, outbuf);\n}\n\nstatic int sshfs_access(const char *path, int mask)\n{\n\tstruct stat stbuf;\n\tint err = 0;\n\n\tif (mask & X_OK) {\n\t\terr = sshfs.op->getattr(path, &stbuf, NULL);\n\t\tif (!err) {\n\t\t\tif (S_ISREG(stbuf.st_mode) &&\n\t\t\t    !(stbuf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))\n\t\t\t\terr = -EACCES;\n\t\t}\n\t}\n\treturn err;\n}\n\nstatic int count_components(const char *p)\n{\n\tint ctr;\n\n\tfor (; *p == '/'; p++);\n\tfor (ctr = 0; *p; ctr++) {\n\t\tfor (; *p && *p != '/'; p++);\n\t\tfor (; *p == '/'; p++);\n\t}\n\treturn ctr;\n}\n\nstatic void strip_common(const char **sp, const char **tp)\n{\n\tconst char *s = *sp;\n\tconst char *t = *tp;\n\tdo {\n\t\tfor (; *s == '/'; s++);\n\t\tfor (; *t == '/'; t++);\n\t\t*tp = t;\n\t\t*sp = s;\n\t\tfor (; *s == *t && *s && *s != '/'; s++, t++);\n\t} while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));\n}\n\nstatic void transform_symlink(const char *path, char **linkp)\n{\n\tconst char *l = *linkp;\n\tconst char *b = sshfs.base_path;\n\tchar *newlink;\n\tchar *s;\n\tint dotdots;\n\tint i;\n\n\tif (l[0] != '/' || b[0] != '/')\n\t\treturn;\n\n\tstrip_common(&l, &b);\n\tif (*b)\n\t\treturn;\n\n\tstrip_common(&l, &path);\n\tdotdots = count_components(path);\n\tif (!dotdots)\n\t\treturn;\n\tdotdots--;\n\n\tnewlink = malloc(dotdots * 3 + strlen(l) + 2);\n\tif (!newlink) {\n\t\tfprintf(stderr, \"sshfs: memory allocation failed\\n\");\n\t\tabort();\n\t}\n\tfor (s = newlink, i = 0; i < dotdots; i++, s += 3)\n\t\tstrcpy(s, \"../\");\n\n\tif (l[0])\n\t\tstrcpy(s, l);\n\telse if (!dotdots)\n\t\tstrcpy(s, \".\");\n\telse\n\t\ts[0] = '\\0';\n\n\tfree(*linkp);\n\t*linkp = newlink;\n}\n\nstatic int sshfs_readlink(const char *path, char *linkbuf, size_t size)\n{\n\tint err;\n\tstruct buffer buf;\n\tstruct buffer name;\n\n\tassert(size > 0);\n\n\tif (sshfs.server_version < 3)\n\t\treturn -EPERM;\n\n\tbuf_init(&buf, 0);\n\tbuf_add_path(&buf, path);\n\t// Commutes with pending write(), so we can use any connection\n\terr = sftp_request(get_conn(NULL, NULL), SSH_FXP_READLINK, &buf, SSH_FXP_NAME, &name);\n\tif (!err) {\n\t\tuint32_t count;\n\t\tchar *link;\n\t\terr = -EIO;\n\t\tif(buf_get_uint32(&name, &count) != -1 && count == 1 &&\n\t\t   buf_get_string(&name, &link) != -1) {\n\t\t\tif (sshfs.transform_symlinks)\n\t\t\t\ttransform_symlink(path, &link);\n\t\t\tstrncpy(linkbuf, link, size - 1);\n\t\t\tlinkbuf[size - 1] = '\\0';\n\t\t\tfree(link);\n\t\t\terr = 0;\n\t\t}\n\t\tbuf_free(&name);\n\t}\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sftp_readdir_send(struct conn *conn, struct request **req,\n\t\t\t     struct buffer *handle)\n{\n\tstruct iovec iov;\n\n\tbuf_to_iov(handle, &iov);\n\treturn sftp_request_send(conn, SSH_FXP_READDIR, &iov, 1, NULL, NULL,\n\t\t\t\t SSH_FXP_NAME, NULL, req);\n}\n\nstatic int sshfs_req_pending(struct request *req)\n{\n\tif (g_hash_table_lookup(sshfs.reqtab, GUINT_TO_POINTER(req->id)))\n\t\treturn 1;\n\telse\n\t\treturn 0;\n}\n\nstatic int sftp_readdir_async(struct conn *conn, struct buffer *handle,\n\t\t\t      void *buf, off_t offset, fuse_fill_dir_t filler)\n{\n\tint err = 0;\n\tint outstanding = 0;\n\tint max = READDIR_START;\n\tGList *list = NULL;\n\n\tint done = 0;\n\n\tassert(offset == 0);\n\twhile (!done || outstanding) {\n\t\tstruct request *req;\n\t\tstruct buffer name;\n\t\tint tmperr;\n\n\t\twhile (!done && outstanding < max) {\n\t\t\ttmperr = sftp_readdir_send(conn, &req, handle);\n\n\t\t\tif (tmperr && !done) {\n\t\t\t\terr = tmperr;\n\t\t\t\tdone = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlist = g_list_append(list, req);\n\t\t\toutstanding++;\n\t\t}\n\n\t\tif (outstanding) {\n\t\t\tGList *first;\n\t\t\t/* wait for response to next request */\n\t\t\tfirst = g_list_first(list);\n\t\t\treq = first->data;\n\t\t\tlist = g_list_delete_link(list, first);\n\t\t\toutstanding--;\n\n\t\t\tif (done) {\n\t\t\t\t/* We need to cache want_reply, since processing\n\t\t\t\t   thread may free req right after unlock() if\n\t\t\t\t   want_reply == 0 */\n\t\t\t\tint want_reply;\n\t\t\t\tpthread_mutex_lock(&sshfs.lock);\n\t\t\t\tif (sshfs_req_pending(req))\n\t\t\t\t\treq->want_reply = 0;\n\t\t\t\twant_reply = req->want_reply;\n\t\t\t\tpthread_mutex_unlock(&sshfs.lock);\n\t\t\t\tif (!want_reply)\n\t\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttmperr = sftp_request_wait(req, SSH_FXP_READDIR,\n\t\t\t\t\t\t    SSH_FXP_NAME, &name);\n\n\t\t\tif (tmperr && !done) {\n\t\t\t\terr = tmperr;\n\t\t\t\tif (err == MY_EOF)\n\t\t\t\t\terr = 0;\n\t\t\t\tdone = 1;\n\t\t\t}\n\t\t\tif (!done) {\n\t\t\t\terr = buf_get_entries(&name, buf, filler);\n\t\t\t\tbuf_free(&name);\n\n\t\t\t\t/* increase number of outstanding requests */\n\t\t\t\tif (max < READDIR_MAX)\n\t\t\t\t\tmax++;\n\n\t\t\t\tif (err)\n\t\t\t\t\tdone = 1;\n\t\t\t}\n\t\t}\n\t}\n\tassert(list == NULL);\n\n\treturn err;\n}\n\nstatic int sftp_readdir_sync(struct conn *conn, struct buffer *handle,\n\t\t\t     void *buf, off_t offset, fuse_fill_dir_t filler)\n{\n\tint err;\n\tassert(offset == 0);\n\tdo {\n\t\tstruct buffer name;\n\t\terr = sftp_request(conn, SSH_FXP_READDIR, handle, SSH_FXP_NAME, &name);\n\t\tif (!err) {\n\t\t\terr = buf_get_entries(&name, buf, filler);\n\t\t\tbuf_free(&name);\n\t\t}\n\t} while (!err);\n\tif (err == MY_EOF)\n\t\terr = 0;\n\n\treturn err;\n}\n\nstatic int sshfs_opendir(const char *path, struct fuse_file_info *fi)\n{\n\tint err;\n\tstruct conn *conn;\n\tstruct buffer buf;\n\tstruct dir_handle *handle;\n\n\thandle = g_new0(struct dir_handle, 1);\n\tif(handle == NULL)\n\t\treturn -ENOMEM;\n\n\t// Commutes with pending write(), so we can use any connection\n\tconn = get_conn(NULL, NULL);\n\tbuf_init(&buf, 0);\n\tbuf_add_path(&buf, path);\n\terr = sftp_request(conn, SSH_FXP_OPENDIR, &buf, SSH_FXP_HANDLE, &handle->buf);\n\tif (!err) {\n\t\tbuf_finish(&handle->buf);\n\t\tpthread_mutex_lock(&sshfs.lock);\n\t\thandle->conn = conn;\n\t\thandle->conn->dir_count++;\n\t\tpthread_mutex_unlock(&sshfs.lock);\n\t\tfi->fh = (unsigned long) handle;\n\t} else\n\t\tg_free(handle);\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_readdir(const char *path, void *dbuf, fuse_fill_dir_t filler,\n\t\t\t off_t offset, struct fuse_file_info *fi,\n\t\t\t enum fuse_readdir_flags flags)\n{\n\t(void) path; (void) flags;\n\tint err;\n\tstruct dir_handle *handle;\n\n\thandle = (struct dir_handle*) fi->fh;\n\n\tif (sshfs.sync_readdir)\n\t\terr = sftp_readdir_sync(handle->conn, &handle->buf, dbuf,\n\t\t\t\t\toffset, filler);\n\telse\n\t\terr = sftp_readdir_async(handle->conn, &handle->buf, dbuf,\n\t\t\t\t\t offset, filler);\n\n\treturn err;\n}\n\nstatic int sshfs_releasedir(const char *path, struct fuse_file_info *fi)\n{\n\t(void) path;\n\tint err;\n\tstruct dir_handle *handle;\n\n\thandle = (struct dir_handle*) fi->fh;\n\terr = sftp_request(handle->conn, SSH_FXP_CLOSE, &handle->buf, 0, NULL);\n\tpthread_mutex_lock(&sshfs.lock);\n\thandle->conn->dir_count--;\n\tpthread_mutex_unlock(&sshfs.lock);\n\tbuf_free(&handle->buf);\n\tg_free(handle);\n\treturn err;\n}\n\n\nstatic int sshfs_mkdir(const char *path, mode_t mode)\n{\n\tint err;\n\tstruct buffer buf;\n\tbuf_init(&buf, 0);\n\tbuf_add_path(&buf, path);\n\tbuf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS);\n\tbuf_add_uint32(&buf, mode);\n\t// Commutes with pending write(), so we can use any connection\n\terr = sftp_request(get_conn(NULL, NULL), SSH_FXP_MKDIR, &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\n\tif (err == -EPERM) {\n\t\tif (sshfs.op->access(path, R_OK) == 0) {\n\t\t\treturn -EEXIST;\n\t\t}\n\t}\n\n\treturn err;\n}\n\nstatic int sshfs_mknod(const char *path, mode_t mode, dev_t rdev)\n{\n\tint err;\n\tstruct conn *conn;\n\tstruct buffer buf;\n\tstruct buffer handle;\n\t(void) rdev;\n\n\tif ((mode & S_IFMT) != S_IFREG)\n\t\treturn -EPERM;\n\n\t// Commutes with pending write(), so we can use any connection\n\tconn = get_conn(NULL, NULL);\n\n\tbuf_init(&buf, 0);\n\tbuf_add_path(&buf, path);\n\tbuf_add_uint32(&buf, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_EXCL);\n\tbuf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS);\n\tbuf_add_uint32(&buf, mode);\n\terr = sftp_request(conn, SSH_FXP_OPEN, &buf, SSH_FXP_HANDLE, &handle);\n\tif (!err) {\n\t\tint err2;\n\t\tbuf_finish(&handle);\n\t\terr2 = sftp_request(conn, SSH_FXP_CLOSE, &handle, SSH_FXP_STATUS, NULL);\n\t\tif (!err)\n\t\t\terr = err2;\n\t\tbuf_free(&handle);\n\t}\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_symlink(const char *from, const char *to)\n{\n\tint err;\n\tstruct buffer buf;\n\n\tif (sshfs.server_version < 3)\n\t\treturn -EPERM;\n\n\t/* openssh sftp server doesn't follow standard: link target and\n\t   link name are mixed up, so we must also be non-standard :( */\n\tbuf_init(&buf, 0);\n\tbuf_add_string(&buf, from);\n\tbuf_add_path(&buf, to);\n\t// Commutes with pending write(), so we can use any connection\n\terr = sftp_request(get_conn(NULL, NULL), SSH_FXP_SYMLINK, &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_unlink(const char *path)\n{\n\tint err;\n\tstruct buffer buf;\n\tbuf_init(&buf, 0);\n\tbuf_add_path(&buf, path);\n\t// Commutes with pending write(), so we can use any connection\n\terr = sftp_request(get_conn(NULL, NULL), SSH_FXP_REMOVE, &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_rmdir(const char *path)\n{\n\tint err;\n\tstruct buffer buf;\n\tbuf_init(&buf, 0);\n\tbuf_add_path(&buf, path);\n\t// Commutes with pending write(), so we can use any connection\n\terr = sftp_request(get_conn(NULL, NULL), SSH_FXP_RMDIR, &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_do_rename(const char *from, const char *to)\n{\n\tint err;\n\tstruct buffer buf;\n\tbuf_init(&buf, 0);\n\tbuf_add_path(&buf, from);\n\tbuf_add_path(&buf, to);\n\t// Commutes with pending write(), so we can use any connection\n\terr = sftp_request(get_conn(NULL, NULL), SSH_FXP_RENAME, &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_ext_posix_rename(const char *from, const char *to)\n{\n\tint err;\n\tstruct buffer buf;\n\tbuf_init(&buf, 0);\n\tbuf_add_string(&buf, SFTP_EXT_POSIX_RENAME);\n\tbuf_add_path(&buf, from);\n\tbuf_add_path(&buf, to);\n\t// Commutes with pending write(), so we can use any connection\n\terr = sftp_request(get_conn(NULL, NULL), SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic void random_string(char *str, int length)\n{\n\tint i;\n\tfor (i = 0; i < length; i++)\n\t\t*str++ = (char)('0' + rand_r(&sshfs.randseed) % 10);\n\t*str = '\\0';\n}\n\nstatic int sshfs_rename(const char *from, const char *to, unsigned int flags)\n{\n\tint err;\n\tstruct conntab_entry *ce;\n\n\tif(flags != 0)\n\t\treturn -EINVAL;\n\n\tif (sshfs.ext_posix_rename)\n\t\terr = sshfs_ext_posix_rename(from, to);\n\telse\n\t\terr = sshfs_do_rename(from, to);\n\tif (err == -EPERM && sshfs.rename_workaround) {\n\t\tsize_t tolen = strlen(to);\n\t\tif (tolen + RENAME_TEMP_CHARS < PATH_MAX) {\n\t\t\tint tmperr;\n\t\t\tchar totmp[PATH_MAX];\n\t\t\tstrcpy(totmp, to);\n\t\t\trandom_string(totmp + tolen, RENAME_TEMP_CHARS);\n\t\t\ttmperr = sshfs_do_rename(to, totmp);\n\t\t\tif (!tmperr) {\n\t\t\t\terr = sshfs_do_rename(from, to);\n\t\t\t\tif (!err)\n\t\t\t\t\terr = sshfs_unlink(totmp);\n\t\t\t\telse\n\t\t\t\t\tsshfs_do_rename(totmp, to);\n\t\t\t}\n\t\t}\n\t}\n\tif (err == -EPERM && sshfs.renamexdev_workaround)\n\t\terr = -EXDEV;\n\n\tif (!err && sshfs.max_conns > 1) {\n\t\tpthread_mutex_lock(&sshfs.lock);\n\t\tce = g_hash_table_lookup(sshfs.conntab, from);\n\t\tif (ce != NULL) {\n\t\t\tg_hash_table_replace(sshfs.conntab, g_strdup(to), ce);\n\t\t\tg_hash_table_remove(sshfs.conntab, from);\n\t\t}\n\t\tpthread_mutex_unlock(&sshfs.lock);\n\t}\n\n\treturn err;\n}\n\nstatic int sshfs_link(const char *from, const char *to)\n{\n\tint err = -ENOSYS;\n\n\tif (sshfs.ext_hardlink && !sshfs.disable_hardlink) {\n\t\tstruct buffer buf;\n\n\t\tbuf_init(&buf, 0);\n\t\tbuf_add_string(&buf, SFTP_EXT_HARDLINK);\n\t\tbuf_add_path(&buf, from);\n\t\tbuf_add_path(&buf, to);\n\t\t// Commutes with pending write(), so we can use any connection\n\t\terr = sftp_request(get_conn(NULL, NULL), SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS,\n\t\t\t\t   NULL);\n\t\tbuf_free(&buf);\n\t}\n\n\treturn err;\n}\n\nstatic inline int sshfs_file_is_conn(struct sshfs_file *sf)\n{\n\tint ret;\n\n\tpthread_mutex_lock(&sshfs.lock);\n\tret = (sf->connver == sf->conn->connver);\n\tpthread_mutex_unlock(&sshfs.lock);\n\n\treturn ret;\n}\n\nstatic inline struct sshfs_file *get_sshfs_file(struct fuse_file_info *fi)\n{\n\treturn (struct sshfs_file *) (uintptr_t) fi->fh;\n}\n\nstatic int sshfs_chmod(const char *path, mode_t mode,\n                       struct fuse_file_info *fi)\n{\n\t(void) fi;\n\tint err;\n\tstruct buffer buf;\n\tstruct sshfs_file *sf = NULL;\n\n\tif (fi != NULL) {\n\t\tsf = get_sshfs_file(fi);\n\t\tif (!sshfs_file_is_conn(sf))\n\t\t\treturn -EIO;\n\t}\n\n\tbuf_init(&buf, 0);\n\tif (sf == NULL)\n\t\tbuf_add_path(&buf, path);\n\telse\n\t\tbuf_add_buf(&buf, &sf->handle);\n\n\tbuf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS);\n\tbuf_add_uint32(&buf, mode);\n\n\t/* FIXME: really needs LSETSTAT extension (debian Bug#640038) */\n\t// Commutes with pending write(), so we can use any connection\n\t// if the file is not open.\n\terr = sftp_request(get_conn(sf, NULL),\n\t\t\t   sf == NULL ? SSH_FXP_SETSTAT : SSH_FXP_FSETSTAT,\n\t\t\t   &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_chown(const char *path, uid_t uid, gid_t gid,\n                       struct fuse_file_info *fi)\n{\n\t(void) fi;\n\tint err;\n\tstruct buffer buf;\n\tstruct sshfs_file *sf = NULL;\n\n\tif (fi != NULL) {\n\t\tsf = get_sshfs_file(fi);\n\t\tif (!sshfs_file_is_conn(sf))\n\t\t\treturn -EIO;\n\t}\n\n\tif (sshfs.remote_uid_detected) {\n\t\tif (uid == sshfs.local_uid)\n\t\t\tuid = sshfs.remote_uid;\n\t\tif (gid == sshfs.local_gid)\n\t\t\tgid = sshfs.remote_gid;\n\t}\n\tif (sshfs.idmap == IDMAP_FILE && sshfs.r_uid_map)\n\t\tif(translate_id(&uid, sshfs.r_uid_map) == -1)\n\t\t\treturn -EPERM;\n\tif (sshfs.idmap == IDMAP_FILE && sshfs.r_gid_map)\n\t\tif (translate_id(&gid, sshfs.r_gid_map) == -1)\n\t\t\treturn -EPERM;\n\n\tbuf_init(&buf, 0);\n\tif (sf == NULL)\n\t\tbuf_add_path(&buf, path);\n\telse\n\t\tbuf_add_buf(&buf, &sf->handle);\n\tbuf_add_uint32(&buf, SSH_FILEXFER_ATTR_UIDGID);\n\tbuf_add_uint32(&buf, uid);\n\tbuf_add_uint32(&buf, gid);\n\n\t// Commutes with pending write(), so we can use any connection\n\t// if the file is not open.\n\terr = sftp_request(get_conn(sf, NULL),\n\t\t\t   sf == NULL ? SSH_FXP_SETSTAT : SSH_FXP_FSETSTAT,\n\t\t\t   &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_truncate_workaround(const char *path, off_t size,\n                                     struct fuse_file_info *fi);\n\nstatic void sshfs_inc_modifver(void)\n{\n\tpthread_mutex_lock(&sshfs.lock);\n\tsshfs.modifver++;\n\tpthread_mutex_unlock(&sshfs.lock);\n}\n\nstatic int sshfs_utimens(const char *path, const struct timespec tv[2],\n\t\t\t struct fuse_file_info *fi)\n{\n\t(void) fi;\n\tint err;\n\tstruct buffer buf;\n\tstruct sshfs_file *sf = NULL;\n\ttime_t asec = tv[0].tv_sec, msec = tv[1].tv_sec;\n\n\tstruct timeval now;\n\tgettimeofday(&now, NULL);\n\tif (asec == 0)\n\t\tasec = now.tv_sec;\n\tif (msec == 0)\n\t\tmsec = now.tv_sec;\n\n\tif (fi != NULL) {\n\t\tsf = get_sshfs_file(fi);\n\t\tif (!sshfs_file_is_conn(sf))\n\t\t\treturn -EIO;\n\t}\n\n\tbuf_init(&buf, 0);\n\tif (sf == NULL)\n\t\tbuf_add_path(&buf, path);\n\telse\n\t\tbuf_add_buf(&buf, &sf->handle);\n\tbuf_add_uint32(&buf, SSH_FILEXFER_ATTR_ACMODTIME);\n\tbuf_add_uint32(&buf, asec);\n\tbuf_add_uint32(&buf, msec);\n\n\terr = sftp_request(get_conn(sf, path),\n\t\t\t   sf == NULL ? SSH_FXP_SETSTAT : SSH_FXP_FSETSTAT,\n\t\t\t   &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_open_common(const char *path, mode_t mode,\n                             struct fuse_file_info *fi)\n{\n\tint err;\n\tint err2;\n\tstruct buffer buf;\n\tstruct buffer outbuf;\n\tstruct stat stbuf;\n\tstruct sshfs_file *sf;\n\tstruct request *open_req;\n\tstruct conntab_entry *ce;\n\tuint32_t pflags = 0;\n\tstruct iovec iov;\n\tuint8_t type;\n\tuint64_t wrctr = 0;\n\n\tif (sshfs.dir_cache)\n\t\twrctr = cache_get_write_ctr();\n\n\tif (sshfs.direct_io)\n\t\tfi->direct_io = 1;\n\n\tif ((fi->flags & O_ACCMODE) == O_RDONLY)\n\t\tpflags = SSH_FXF_READ;\n\telse if((fi->flags & O_ACCMODE) == O_WRONLY)\n\t\tpflags = SSH_FXF_WRITE;\n\telse if ((fi->flags & O_ACCMODE) == O_RDWR)\n\t\tpflags = SSH_FXF_READ | SSH_FXF_WRITE;\n\telse\n\t\treturn -EINVAL;\n\n\tif (fi->flags & O_CREAT)\n\t\tpflags |= SSH_FXF_CREAT;\n\n\tif (fi->flags & O_EXCL)\n\t\tpflags |= SSH_FXF_EXCL;\n\n\tif (fi->flags & O_TRUNC)\n\t\tpflags |= SSH_FXF_TRUNC;\n\n\tif (fi->flags & O_APPEND)\n\t\tpflags |= SSH_FXF_APPEND;\n\n\tsf = g_new0(struct sshfs_file, 1);\n\tlist_init(&sf->write_reqs);\n\tpthread_cond_init(&sf->write_finished, NULL);\n\t/* Assume random read after open */\n\tsf->is_seq = 0;\n\tsf->next_pos = 0;\n\tpthread_mutex_lock(&sshfs.lock);\n\tsf->modifver= sshfs.modifver;\n\tif (sshfs.max_conns > 1) {\n\t\tce = g_hash_table_lookup(sshfs.conntab, path);\n\t\tif (!ce) {\n\t\t\tce = g_malloc(sizeof(struct conntab_entry));\n\t\t\tce->refcount = 0;\n\t\t\tce->conn = get_conn(NULL, NULL);\n\t\t\tg_hash_table_insert(sshfs.conntab, g_strdup(path), ce);\n\t\t}\n\t\tsf->conn = ce->conn;\n\t\tce->refcount++;\n\t\tsf->conn->file_count++;\n\t\tassert(sf->conn->file_count > 0);\n\t} else {\n\t\tsf->conn = &sshfs.conns[0];\n\t\tce = NULL; // only to silence compiler warning\n\t}\n\tsf->connver = sf->conn->connver;\n\tpthread_mutex_unlock(&sshfs.lock);\n\tbuf_init(&buf, 0);\n\tbuf_add_path(&buf, path);\n\tbuf_add_uint32(&buf, pflags);\n\tbuf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS);\n\tbuf_add_uint32(&buf, mode);\n\tbuf_to_iov(&buf, &iov);\n\tsftp_request_send(sf->conn, SSH_FXP_OPEN, &iov, 1, NULL, NULL, 1, NULL,\n\t\t\t  &open_req);\n\tbuf_clear(&buf);\n\tbuf_add_path(&buf, path);\n\ttype = sshfs.follow_symlinks ? SSH_FXP_STAT : SSH_FXP_LSTAT;\n\terr2 = sftp_request(sf->conn, type, &buf, SSH_FXP_ATTRS, &outbuf);\n\tif (!err2) {\n\t\terr2 = buf_get_attrs(&outbuf, &stbuf, NULL);\n\t\tbuf_free(&outbuf);\n\t}\n\terr = sftp_request_wait(open_req, SSH_FXP_OPEN, SSH_FXP_HANDLE,\n\t\t\t\t&sf->handle);\n\tif (!err && err2) {\n\t\tbuf_finish(&sf->handle);\n\t\tsftp_request(sf->conn, SSH_FXP_CLOSE, &sf->handle, 0, NULL);\n\t\tbuf_free(&sf->handle);\n\t\terr = err2;\n\t}\n\n\tif (!err) {\n\t\tif (sshfs.dir_cache)\n\t\t\tcache_add_attr(path, &stbuf, wrctr);\n\t\tbuf_finish(&sf->handle);\n\t\tfi->fh = (unsigned long) sf;\n\t} else {\n\t\tif (sshfs.dir_cache)\n\t\t\tcache_invalidate(path);\n\t\tif (sshfs.max_conns > 1) {\n\t\t\tpthread_mutex_lock(&sshfs.lock);\n\t\t\tsf->conn->file_count--;\n\t\t\tce->refcount--;\n\t\t\tif(ce->refcount == 0) {\n\t\t\t\tg_hash_table_remove(sshfs.conntab, path);\n\t\t\t\tg_free(ce);\n\t\t\t}\n\t\t\tpthread_mutex_unlock(&sshfs.lock);\n\t\t}\n\t\tg_free(sf);\n\t}\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_open(const char *path, struct fuse_file_info *fi)\n{\n\treturn sshfs_open_common(path, 0, fi);\n}\n\nstatic int sshfs_flush(const char *path, struct fuse_file_info *fi)\n{\n\tint err;\n\tstruct sshfs_file *sf = get_sshfs_file(fi);\n\tstruct list_head write_reqs;\n\tstruct list_head *curr_list;\n\n\tif (!sshfs_file_is_conn(sf))\n\t\treturn -EIO;\n\n\tif (sshfs.sync_write)\n\t\treturn 0;\n\n\t(void) path;\n\tpthread_mutex_lock(&sshfs.lock);\n\tif (!list_empty(&sf->write_reqs)) {\n\t\tcurr_list = sf->write_reqs.prev;\n\t\tlist_del(&sf->write_reqs);\n\t\tlist_init(&sf->write_reqs);\n\t\tlist_add(&write_reqs, curr_list);\n\t\twhile (!list_empty(&write_reqs))\n\t\t\tpthread_cond_wait(&sf->write_finished, &sshfs.lock);\n\t}\n\terr = sf->write_error;\n\tsf->write_error = 0;\n\tpthread_mutex_unlock(&sshfs.lock);\n\treturn err;\n}\n\nstatic int sshfs_fsync(const char *path, int isdatasync,\n                       struct fuse_file_info *fi)\n{\n\tint err;\n\t(void) isdatasync;\n\n\terr = sshfs_flush(path, fi);\n\tif (err)\n\t\treturn err;\n\n\tif (!sshfs.ext_fsync)\n\t\treturn err;\n\n\tstruct buffer buf;\n\tstruct sshfs_file *sf = get_sshfs_file(fi);\n\tbuf_init(&buf, 0);\n\tbuf_add_string(&buf, SFTP_EXT_FSYNC);\n\tbuf_add_buf(&buf, &sf->handle);\n\terr = sftp_request(sf->conn, SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_release(const char *path, struct fuse_file_info *fi)\n{\n\tstruct sshfs_file *sf = get_sshfs_file(fi);\n\tstruct buffer *handle = &sf->handle;\n\tstruct conntab_entry *ce;\n\tif (sshfs_file_is_conn(sf)) {\n\t\tsshfs_flush(path, fi);\n\t\tsftp_request(sf->conn, SSH_FXP_CLOSE, handle, 0, NULL);\n\t}\n\tbuf_free(handle);\n\tchunk_put_locked(sf->readahead);\n\tif (sshfs.max_conns > 1) {\n\t\tpthread_mutex_lock(&sshfs.lock);\n\t\tsf->conn->file_count--;\n\t\tce = g_hash_table_lookup(sshfs.conntab, path);\n\t\tce->refcount--;\n\t\tif(ce->refcount == 0) {\n\t\t\tg_hash_table_remove(sshfs.conntab, path);\n\t\t\tg_free(ce);\n\t\t}\n\t\tpthread_mutex_unlock(&sshfs.lock);\n\t}\n\tg_free(sf);\n\treturn 0;\n}\n\nstatic void sshfs_read_end(struct request *req)\n{\n\tstruct read_req *rreq = (struct read_req *) req->data;\n\tif (req->error)\n\t\trreq->res = req->error;\n\telse if (req->replied) {\n\t\trreq->res = -EIO;\n\n\t\tif (req->reply_type == SSH_FXP_STATUS) {\n\t\t\tuint32_t serr;\n\t\t\tif (buf_get_uint32(&req->reply, &serr) != -1) {\n\t\t\t\tif (serr == SSH_FX_EOF)\n\t\t\t\t\trreq->res = 0;\n\t\t\t\telse\n\t\t\t\t\trreq->res = -sftp_error_to_errno(serr);\n\t\t\t}\n\t\t} else if (req->reply_type == SSH_FXP_DATA) {\n\t\t\tuint32_t retsize;\n\t\t\tif (buf_get_uint32(&req->reply, &retsize) != -1) {\n\t\t\t\tif (retsize > rreq->size) {\n\t\t\t\t\tfprintf(stderr, \"long read\\n\");\n\t\t\t\t} else if (buf_check_get(&req->reply, retsize) != -1) {\n\t\t\t\t\trreq->res = retsize;\n\t\t\t\t\trreq->data = req->reply;\n\t\t\t\t\tbuf_init(&req->reply, 0);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfprintf(stderr, \"protocol error\\n\");\n\t\t}\n\t} else {\n\t\trreq->res = -EIO;\n\t}\n\n\trreq->sio->num_reqs--;\n\tif (!rreq->sio->num_reqs)\n\t\tpthread_cond_broadcast(&rreq->sio->finished);\n}\n\nstatic void sshfs_read_begin(struct request *req)\n{\n\tstruct read_req *rreq = (struct read_req *) req->data;\n\trreq->sio->num_reqs++;\n}\n\nstatic struct read_chunk *sshfs_send_read(struct sshfs_file *sf, size_t size,\n\t\t\t\t\t  off_t offset)\n{\n\tstruct read_chunk *chunk = g_new0(struct read_chunk, 1);\n\tstruct buffer *handle = &sf->handle;\n\n\tpthread_cond_init(&chunk->sio.finished, NULL);\n\tlist_init(&chunk->reqs);\n\tchunk->size = size;\n\tchunk->offset = offset;\n\tchunk->refs = 1;\n\n\twhile (size) {\n\t\tint err;\n\t\tstruct buffer buf;\n\t\tstruct iovec iov[1];\n\t\tstruct read_req *rreq;\n\t\tsize_t bsize = size < sshfs.max_read ? size : sshfs.max_read;\n\n\t\trreq = g_new0(struct read_req, 1);\n\t\trreq->sio = &chunk->sio;\n\t\trreq->size = bsize;\n\t\tbuf_init(&rreq->data, 0);\n\t\tlist_add(&rreq->list, &chunk->reqs);\n\n\t\tbuf_init(&buf, 0);\n\t\tbuf_add_buf(&buf, handle);\n\t\tbuf_add_uint64(&buf, offset);\n\t\tbuf_add_uint32(&buf, bsize);\n\t\tbuf_to_iov(&buf, &iov[0]);\n\t\terr = sftp_request_send(sf->conn, SSH_FXP_READ, iov, 1,\n\t\t\t\t\tsshfs_read_begin,\n\t\t\t\t\tsshfs_read_end,\n\t\t\t\t\t0, rreq, NULL);\n\n\t\tbuf_free(&buf);\n\t\tif (err)\n\t\t\tbreak;\n\n\t\tsize -= bsize;\n\t\toffset += bsize;\n\t}\n\n\treturn chunk;\n}\n\nstatic int wait_chunk(struct read_chunk *chunk, char *buf, size_t size)\n{\n\tint res = 0;\n\tstruct read_req *rreq;\n\n\tpthread_mutex_lock(&sshfs.lock);\n\twhile (chunk->sio.num_reqs)\n\t       pthread_cond_wait(&chunk->sio.finished, &sshfs.lock);\n\tpthread_mutex_unlock(&sshfs.lock);\n\n\n\tif (chunk->sio.error) {\n\t\tif (chunk->sio.error != MY_EOF)\n\t\t\tres = chunk->sio.error;\n\n\t\tgoto out;\n\t}\n\n\twhile (!list_empty(&chunk->reqs) && size) {\n\t\trreq = list_entry(chunk->reqs.prev, struct read_req, list);\n\n\t\tif (rreq->res < 0) {\n\t\t\tchunk->sio.error = rreq->res;\n\t\t\tbreak;\n\t\t} if (rreq->res == 0) {\n\t\t\tchunk->sio.error = MY_EOF;\n\t\t\tbreak;\n\t\t} else if (size < (size_t) rreq->res) {\n\t\t\tbuf_get_mem(&rreq->data, buf, size);\n\t\t\trreq->res -= size;\n\t\t\trreq->size -= size;\n\t\t\tres += size;\n\t\t\tbreak;\n\t\t} else {\n\t\t\tbuf_get_mem(&rreq->data, buf, rreq->res);\n\t\t\tres += rreq->res;\n\t\t\tif ((size_t) rreq->res < rreq->size) {\n\t\t\t\tchunk->sio.error = MY_EOF;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbuf += rreq->res;\n\t\t\tsize -= rreq->res;\n\t\t\tlist_del(&rreq->list);\n\t\t\tbuf_free(&rreq->data);\n\t\t\tg_free(rreq);\n\t\t}\n\t}\n\n\tif (res > 0) {\n\t\tchunk->offset += res;\n\t\tchunk->size -= res;\n\t}\n\nout:\n\tchunk_put_locked(chunk);\n\treturn res;\n}\n\nstatic int sshfs_sync_read(struct sshfs_file *sf, char *buf, size_t size,\n                           off_t offset)\n{\n\tstruct read_chunk *chunk;\n\n\tchunk = sshfs_send_read(sf, size, offset);\n\treturn wait_chunk(chunk, buf, size);\n}\n\nstatic void submit_read(struct sshfs_file *sf, size_t size, off_t offset,\n                        struct read_chunk **chunkp)\n{\n\tstruct read_chunk *chunk;\n\n\tchunk = sshfs_send_read(sf, size, offset);\n\tpthread_mutex_lock(&sshfs.lock);\n\tchunk->modifver = sshfs.modifver;\n\tchunk_put(*chunkp);\n\t*chunkp = chunk;\n\tchunk->refs++;\n\tpthread_mutex_unlock(&sshfs.lock);\n}\n\nstatic struct read_chunk *search_read_chunk(struct sshfs_file *sf, off_t offset)\n{\n\tstruct read_chunk *ch = sf->readahead;\n\tif (ch && ch->offset == offset && ch->modifver == sshfs.modifver) {\n\t\tch->refs++;\n\t\treturn ch;\n\t} else\n\t\treturn NULL;\n}\n\nstatic int sshfs_async_read(struct sshfs_file *sf, char *rbuf, size_t size,\n                            off_t offset)\n{\n\tint res = 0;\n\tsize_t total = 0;\n\tstruct read_chunk *chunk;\n\tstruct read_chunk *chunk_prev = NULL;\n\tsize_t origsize = size;\n\tint curr_is_seq;\n\n\tpthread_mutex_lock(&sshfs.lock);\n\tcurr_is_seq = sf->is_seq;\n\tsf->is_seq = (sf->next_pos == offset && sf->modifver == sshfs.modifver);\n\tsf->next_pos = offset + size;\n\tsf->modifver = sshfs.modifver;\n\tchunk = search_read_chunk(sf, offset);\n\tpthread_mutex_unlock(&sshfs.lock);\n\n\tif (chunk && chunk->size < size) {\n\t\tchunk_prev = chunk;\n\t\tsize -= chunk->size;\n\t\toffset += chunk->size;\n\t\tchunk = NULL;\n\t}\n\n\tif (!chunk)\n\t\tsubmit_read(sf, size, offset, &chunk);\n\n\tif (curr_is_seq && chunk && chunk->size <= size)\n\t\tsubmit_read(sf, origsize, offset + size, &sf->readahead);\n\n\tif (chunk_prev) {\n\t\tsize_t prev_size = chunk_prev->size;\n\t\tres = wait_chunk(chunk_prev, rbuf, prev_size);\n\t\tif (res < (int) prev_size) {\n\t\t\tchunk_put_locked(chunk);\n\t\t\treturn res;\n\t\t}\n\t\trbuf += res;\n\t\ttotal += res;\n\t}\n\tres = wait_chunk(chunk, rbuf, size);\n\tif (res > 0)\n\t\ttotal += res;\n\tif (res < 0)\n\t\treturn res;\n\n\treturn total;\n}\n\nstatic int sshfs_read(const char *path, char *rbuf, size_t size, off_t offset,\n                      struct fuse_file_info *fi)\n{\n\tstruct sshfs_file *sf = get_sshfs_file(fi);\n\t(void) path;\n\n\tif (!sshfs_file_is_conn(sf))\n\t\treturn -EIO;\n\n\tif (sshfs.sync_read)\n\t\treturn sshfs_sync_read(sf, rbuf, size, offset);\n\telse\n\t\treturn sshfs_async_read(sf, rbuf, size, offset);\n}\n\nstatic void sshfs_write_begin(struct request *req)\n{\n\tstruct sshfs_file *sf = (struct sshfs_file *) req->data;\n\tlist_add(&req->list, &sf->write_reqs);\n}\n\nstatic void sshfs_write_end(struct request *req)\n{\n\tuint32_t serr;\n\tstruct sshfs_file *sf = (struct sshfs_file *) req->data;\n\n\tif (req->error)\n\t\tsf->write_error = req->error;\n\telse if (req->replied) {\n\t\tif (req->reply_type != SSH_FXP_STATUS) {\n\t\t\tfprintf(stderr, \"protocol error\\n\");\n\t\t} else if (buf_get_uint32(&req->reply, &serr) != -1 &&\n\t\t\tserr != SSH_FX_OK) {\n\t\t\tsf->write_error = -EIO;\n\t\t}\n\t}\n\tlist_del(&req->list);\n\tpthread_cond_broadcast(&sf->write_finished);\n}\n\nstatic int sshfs_async_write(struct sshfs_file *sf, const char *wbuf,\n\t\t\t     size_t size, off_t offset)\n{\n\tint err = 0;\n\tstruct buffer *handle = &sf->handle;\n\n\twhile (!err && size) {\n\t\tstruct buffer buf;\n\t\tstruct iovec iov[2];\n\t\tsize_t bsize = size < sshfs.max_write ? size : sshfs.max_write;\n\n\t\tbuf_init(&buf, 0);\n\t\tbuf_add_buf(&buf, handle);\n\t\tbuf_add_uint64(&buf, offset);\n\t\tbuf_add_uint32(&buf, bsize);\n\t\tbuf_to_iov(&buf, &iov[0]);\n\t\tiov[1].iov_base = (void *) wbuf;\n\t\tiov[1].iov_len = bsize;\n\t\terr = sftp_request_send(sf->conn, SSH_FXP_WRITE, iov, 2,\n\t\t\t\t\tsshfs_write_begin, sshfs_write_end,\n\t\t\t\t\t0, sf, NULL);\n\t\tbuf_free(&buf);\n\t\tsize -= bsize;\n\t\twbuf += bsize;\n\t\toffset += bsize;\n\t}\n\n\treturn err;\n}\n\nstatic void sshfs_sync_write_begin(struct request *req)\n{\n\tstruct sshfs_io *sio = (struct sshfs_io *) req->data;\n\tsio->num_reqs++;\n}\n\nstatic void sshfs_sync_write_end(struct request *req)\n{\n\tuint32_t serr;\n\tstruct sshfs_io *sio = (struct sshfs_io *) req->data;\n\n\tif (req->error) {\n\t\tsio->error = req->error;\n\t} else if (req->replied) {\n\t\tif (req->reply_type != SSH_FXP_STATUS) {\n\t\t\tfprintf(stderr, \"protocol error\\n\");\n\t\t} else if (buf_get_uint32(&req->reply, &serr) != -1 &&\n\t\t\tserr != SSH_FX_OK) {\n\t\t\tsio->error = -EIO;\n\t\t}\n\t}\n\tsio->num_reqs--;\n\tif (!sio->num_reqs)\n\t\tpthread_cond_broadcast(&sio->finished);\n}\n\n\nstatic int sshfs_sync_write(struct sshfs_file *sf, const char *wbuf,\n\t\t\t    size_t size, off_t offset)\n{\n\tint err = 0;\n\tstruct buffer *handle = &sf->handle;\n\tstruct sshfs_io sio = { .error = 0, .num_reqs = 0 };\n\n\tpthread_cond_init(&sio.finished, NULL);\n\n\twhile (!err && size) {\n\t\tstruct buffer buf;\n\t\tstruct iovec iov[2];\n\t\tsize_t bsize = size < sshfs.max_write ? size : sshfs.max_write;\n\n\t\tbuf_init(&buf, 0);\n\t\tbuf_add_buf(&buf, handle);\n\t\tbuf_add_uint64(&buf, offset);\n\t\tbuf_add_uint32(&buf, bsize);\n\t\tbuf_to_iov(&buf, &iov[0]);\n\t\tiov[1].iov_base = (void *) wbuf;\n\t\tiov[1].iov_len = bsize;\n\t\terr = sftp_request_send(sf->conn, SSH_FXP_WRITE, iov, 2,\n\t\t\t\t\tsshfs_sync_write_begin,\n\t\t\t\t\tsshfs_sync_write_end,\n\t\t\t\t\t0, &sio, NULL);\n\t\tbuf_free(&buf);\n\t\tsize -= bsize;\n\t\twbuf += bsize;\n\t\toffset += bsize;\n\t}\n\n\tpthread_mutex_lock(&sshfs.lock);\n\twhile (sio.num_reqs)\n\t       pthread_cond_wait(&sio.finished, &sshfs.lock);\n\tpthread_mutex_unlock(&sshfs.lock);\n\n\tif (!err)\n\t\terr = sio.error;\n\n\treturn err;\n}\n\nstatic int sshfs_write(const char *path, const char *wbuf, size_t size,\n                       off_t offset, struct fuse_file_info *fi)\n{\n\tint err;\n\tstruct sshfs_file *sf = get_sshfs_file(fi);\n\n\t(void) path;\n\n\tif (!sshfs_file_is_conn(sf))\n\t\treturn -EIO;\n\n\tsshfs_inc_modifver();\n\n\tif (!sshfs.sync_write && !sf->write_error)\n\t\terr = sshfs_async_write(sf, wbuf, size, offset);\n\telse\n\t\terr = sshfs_sync_write(sf, wbuf, size, offset);\n\n\treturn err ? err : (int) size;\n}\n\nstatic int sshfs_ext_statvfs(const char *path, struct statvfs *stbuf)\n{\n\tint err;\n\tstruct buffer buf;\n\tstruct buffer outbuf;\n\tbuf_init(&buf, 0);\n\tbuf_add_string(&buf, SFTP_EXT_STATVFS);\n\tbuf_add_path(&buf, path);\n\terr = sftp_request(get_conn(NULL, NULL), SSH_FXP_EXTENDED, &buf,\n\t\t\t   SSH_FXP_EXTENDED_REPLY, &outbuf);\n\tif (!err) {\n\t\tif (buf_get_statvfs(&outbuf, stbuf) == -1)\n\t\t\terr = -EIO;\n\t\tbuf_free(&outbuf);\n\t}\n\tbuf_free(&buf);\n\treturn err;\n}\n\n\nstatic int sshfs_statfs(const char *path, struct statvfs *buf)\n{\n\tif (sshfs.ext_statvfs)\n\t\treturn sshfs_ext_statvfs(path, buf);\n\n\tbuf->f_namemax = 255;\n\tbuf->f_bsize = sshfs.blksize;\n\t/*\n\t * df seems to use f_bsize instead of f_frsize, so make them\n\t * the same\n\t */\n\tbuf->f_frsize = buf->f_bsize;\n\tbuf->f_blocks = buf->f_bfree =  buf->f_bavail =\n\t\t1000ULL * 1024 * 1024 * 1024 / buf->f_frsize;\n\tbuf->f_files = buf->f_ffree = 1000000000;\n\treturn 0;\n}\n\nstatic int sshfs_create(const char *path, mode_t mode,\n                        struct fuse_file_info *fi)\n{\n\tif (sshfs.createmode_workaround)\n\t\tmode = 0;\n\n\treturn sshfs_open_common(path, mode, fi);\n}\n\nstatic int sshfs_truncate(const char *path, off_t size,\n\t\t\t  struct fuse_file_info *fi)\n{\n\tint err;\n\tstruct buffer buf;\n\tstruct sshfs_file *sf = NULL;\n\n\tif (fi != NULL) {\n\t\tsf = get_sshfs_file(fi);\n\t\tif (!sshfs_file_is_conn(sf))\n\t\t\treturn -EIO;\n\t}\n\n\tsshfs_inc_modifver();\n\tif (sshfs.truncate_workaround)\n\t\treturn sshfs_truncate_workaround(path, size, fi);\n\n\tbuf_init(&buf, 0);\n\n\tif (sf != NULL)\n\t\tbuf_add_buf(&buf, &sf->handle);\n\telse\n\t\tbuf_add_path(&buf, path);\n\n\tbuf_add_uint32(&buf, SSH_FILEXFER_ATTR_SIZE);\n\tbuf_add_uint64(&buf, size);\n\terr = sftp_request(get_conn(sf, path),\n\t\t\t   sf == NULL ? SSH_FXP_SETSTAT : SSH_FXP_FSETSTAT,\n\t\t\t   &buf, SSH_FXP_STATUS, NULL);\n\tbuf_free(&buf);\n\n\treturn err;\n}\n\nstatic int sshfs_getattr(const char *path, struct stat *stbuf,\n\t\t\t struct fuse_file_info *fi)\n{\n\tint err;\n\tstruct buffer buf;\n\tstruct buffer outbuf;\n\tstruct sshfs_file *sf = NULL;\n\n\tif (fi != NULL && !sshfs.fstat_workaround) {\n\t\tsf = get_sshfs_file(fi);\n\t\tif (!sshfs_file_is_conn(sf))\n\t\t\treturn -EIO;\n\t}\n\n\tbuf_init(&buf, 0);\n\tif(sf == NULL) {\n\t\tbuf_add_path(&buf, path);\n\t\terr = sftp_request(get_conn(sf, path),\n\t\t\t\t   sshfs.follow_symlinks ? SSH_FXP_STAT : SSH_FXP_LSTAT,\n\t\t\t\t   &buf, SSH_FXP_ATTRS, &outbuf);\n\t}\n\telse {\n\t\tbuf_add_buf(&buf, &sf->handle);\n\t\terr = sftp_request(sf->conn, SSH_FXP_FSTAT, &buf,\n\t\t\t\t   SSH_FXP_ATTRS, &outbuf);\n\t}\n\tif (!err) {\n\t\terr = buf_get_attrs(&outbuf, stbuf, NULL);\n#ifdef __APPLE__\n\t\tstbuf->st_blksize = 0;\n#endif\n\t\tbuf_free(&outbuf);\n\t}\n\tbuf_free(&buf);\n\treturn err;\n}\n\nstatic int sshfs_truncate_zero(const char *path)\n{\n\tint err;\n\tstruct fuse_file_info fi;\n\n\tfi.flags = O_WRONLY | O_TRUNC;\n\terr = sshfs_open(path, &fi);\n\tif (!err)\n\t\tsshfs_release(path, &fi);\n\n\treturn err;\n}\n\nstatic size_t calc_buf_size(off_t size, off_t offset)\n{\n\treturn offset + sshfs.max_read < size ? sshfs.max_read : size - offset;\n}\n\nstatic int sshfs_truncate_shrink(const char *path, off_t size)\n{\n\tint res;\n\tchar *data;\n\toff_t offset;\n\tstruct fuse_file_info fi;\n\n\tdata = calloc(size, 1);\n\tif (!data)\n\t\treturn -ENOMEM;\n\n\tfi.flags = O_RDONLY;\n\tres = sshfs_open(path, &fi);\n\tif (res)\n\t\tgoto out;\n\n\tfor (offset = 0; offset < size; offset += res) {\n\t\tsize_t bufsize = calc_buf_size(size, offset);\n\t\tres = sshfs_read(path, data + offset, bufsize, offset, &fi);\n\t\tif (res <= 0)\n\t\t\tbreak;\n\t}\n\tsshfs_release(path, &fi);\n\tif (res < 0)\n\t\tgoto out;\n\n\tfi.flags = O_WRONLY | O_TRUNC;\n\tres = sshfs_open(path, &fi);\n\tif (res)\n\t\tgoto out;\n\n\tfor (offset = 0; offset < size; offset += res) {\n\t\tsize_t bufsize = calc_buf_size(size, offset);\n\t\tres = sshfs_write(path, data + offset, bufsize, offset, &fi);\n\t\tif (res < 0)\n\t\t\tbreak;\n\t}\n\tif (res >= 0)\n\t\tres = sshfs_flush(path, &fi);\n\tsshfs_release(path, &fi);\n\nout:\n\tfree(data);\n\treturn res;\n}\n\nstatic int sshfs_truncate_extend(const char *path, off_t size,\n                                 struct fuse_file_info *fi)\n{\n\tint res;\n\tchar c = 0;\n\tstruct fuse_file_info tmpfi;\n\tstruct fuse_file_info *openfi = fi;\n\tif (!fi) {\n\t\topenfi = &tmpfi;\n\t\topenfi->flags = O_WRONLY;\n\t\tres = sshfs_open(path, openfi);\n\t\tif (res)\n\t\t\treturn res;\n\t}\n\tres = sshfs_write(path, &c, 1, size - 1, openfi);\n\tif (res == 1)\n\t\tres = sshfs_flush(path, openfi);\n\tif (!fi)\n\t\tsshfs_release(path, openfi);\n\n\treturn res;\n}\n\n/*\n * Work around broken sftp servers which don't handle\n * SSH_FILEXFER_ATTR_SIZE in SETSTAT request.\n *\n * If new size is zero, just open the file with O_TRUNC.\n *\n * If new size is smaller than current size, then copy file locally,\n * then open/trunc and send it back.\n *\n * If new size is greater than current size, then write a zero byte to\n * the new end of the file.\n */\nstatic int sshfs_truncate_workaround(const char *path, off_t size,\n                                     struct fuse_file_info *fi)\n{\n\tif (size == 0)\n\t\treturn sshfs_truncate_zero(path);\n\telse {\n\t\tstruct stat stbuf;\n\t\tint err;\n\t\terr = sshfs_getattr(path, &stbuf, fi);\n\t\tif (err)\n\t\t\treturn err;\n\t\tif (stbuf.st_size == size)\n\t\t\treturn 0;\n\t\telse if (stbuf.st_size > size)\n\t\t\treturn sshfs_truncate_shrink(path, size);\n\t\telse\n\t\t\treturn sshfs_truncate_extend(path, size, fi);\n\t}\n}\n\nstatic int processing_init(void)\n{\n\tint i;\n\n\tsignal(SIGPIPE, SIG_IGN);\n\n\tpthread_mutex_init(&sshfs.lock, NULL);\n\tfor (i = 0; i < sshfs.max_conns; i++)\n\t\tpthread_mutex_init(&sshfs.conns[i].lock_write, NULL);\n\tpthread_cond_init(&sshfs.outstanding_cond, NULL);\n\tsshfs.reqtab = g_hash_table_new(NULL, NULL);\n\tif (!sshfs.reqtab) {\n\t\tfprintf(stderr, \"failed to create hash table\\n\");\n\t\treturn -1;\n\t}\n\tif (sshfs.max_conns > 1) {\n\t\tsshfs.conntab = g_hash_table_new_full(g_str_hash, g_str_equal,\n\t\t\t\t\t\t      g_free, NULL);\n\t\tif (!sshfs.conntab) {\n\t\t\tfprintf(stderr, \"failed to create hash table\\n\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic struct fuse_operations sshfs_oper = {\n\t\t.init       = sshfs_init,\n\t\t.getattr    = sshfs_getattr,\n\t\t.access     = sshfs_access,\n\t\t.opendir    = sshfs_opendir,\n\t\t.readdir    = sshfs_readdir,\n\t\t.releasedir = sshfs_releasedir,\n\t\t.readlink   = sshfs_readlink,\n\t\t.mknod      = sshfs_mknod,\n\t\t.mkdir      = sshfs_mkdir,\n\t\t.symlink    = sshfs_symlink,\n\t\t.unlink     = sshfs_unlink,\n\t\t.rmdir      = sshfs_rmdir,\n\t\t.rename     = sshfs_rename,\n\t\t.link       = sshfs_link,\n\t\t.chmod      = sshfs_chmod,\n\t\t.chown      = sshfs_chown,\n\t\t.truncate   = sshfs_truncate,\n\t\t.utimens    = sshfs_utimens,\n\t\t.open       = sshfs_open,\n\t\t.flush      = sshfs_flush,\n\t\t.fsync      = sshfs_fsync,\n\t\t.release    = sshfs_release,\n\t\t.read       = sshfs_read,\n\t\t.write      = sshfs_write,\n\t\t.statfs     = sshfs_statfs,\n\t\t.create     = sshfs_create,\n};\n\nstatic void usage(const char *progname)\n{\n\tprintf(\n\"usage: %s [user@]host:[dir] mountpoint [options]\\n\"\n\"\\n\"\n\"    -h   --help            print help\\n\"\n\"    -V   --version         print version\\n\"\n\"    -f                     foreground operation\\n\"\n\"    -s                     disable multi-threaded operation\\n\"\n\"    -p PORT                equivalent to '-o port=PORT'\\n\"\n\"    -C                     equivalent to '-o compression=yes'\\n\"\n\"    -F ssh_configfile      specifies alternative ssh configuration file\\n\"\n\"    -1                     equivalent to '-o ssh_protocol=1'\\n\"\n\"    -o opt,[opt...]        mount options\\n\"\n\"    -o reconnect           reconnect to server\\n\"\n\"    -o delay_connect       delay connection to server\\n\"\n\"    -o sshfs_sync          synchronous writes\\n\"\n\"    -o no_readahead        synchronous reads (no speculative readahead)\\n\"\n\"    -o sync_readdir        synchronous readdir\\n\"\n\"    -d, --debug            print some debugging information (implies -f)\\n\"\n\"    -v, --verbose          print ssh replies and messages\\n\"\n\"    -o dir_cache=BOOL      enable caching of directory contents (names,\\n\"\n\"                           attributes, symlink targets) {yes,no} (default: yes)\\n\"\n\"    -o dcache_max_size=N   sets the maximum size of the directory cache (default: 10000)\\n\"\n\"    -o dcache_timeout=N    sets timeout for directory cache in seconds (default: 20)\\n\"\n\"    -o dcache_{stat,link,dir}_timeout=N\\n\"\n\"                           sets separate timeout for {attributes, symlinks, names}\\n\"\n\"    -o dcache_clean_interval=N\\n\"\n\"                           sets the interval for automatic cleaning of the\\n\"\n\"                           cache (default: 60)\\n\"\n\"    -o dcache_min_clean_interval=N\\n\"\n\"                           sets the interval for forced cleaning of the\\n\"\n\"                           cache if full (default: 5)\\n\"\n\"    -o direct_io           enable direct i/o\\n\"\n\"    -o workaround=LIST     colon separated list of workarounds\\n\"\n\"             none             no workarounds enabled\\n\"\n\"             [no]rename       fix renaming to existing file (default: off)\\n\"\n\"             [no]renamexdev   fix moving across filesystems (default: off)\\n\"\n\"             [no]truncate     fix truncate for old servers (default: off)\\n\"\n\"             [no]buflimit     fix buffer fillup bug in server (default: off)\\n\"\n\"             [no]fstat        always use stat() instead of fstat() (default: off)\\n\"\n\"             [no]createmode   always pass mode 0 to create (default: off)\\n\"\n\"    -o idmap=TYPE          user/group ID mapping (default: \" IDMAP_DEFAULT \")\\n\"\n\"             none             no translation of the ID space\\n\"\n\"             user             only translate UID/GID of connecting user\\n\"\n\"             file             translate UIDs/GIDs contained in uidfile/gidfile\\n\"\n\"    -o uidfile=FILE        file containing username:remote_uid mappings\\n\"\n\"    -o gidfile=FILE        file containing groupname:remote_gid mappings\\n\"\n\"    -o nomap=TYPE          with idmap=file, how to handle missing mappings\\n\"\n\"             ignore           don't do any re-mapping\\n\"\n\"             error            return an error (default)\\n\"\n\"    -o ssh_command=CMD     execute CMD instead of 'ssh'\\n\"\n\"    -o ssh_protocol=N      ssh protocol to use (default: 2)\\n\"\n\"    -o sftp_server=SERV    path to sftp server or subsystem (default: sftp)\\n\"\n\"    -o directport=PORT     directly connect to PORT bypassing ssh\\n\"\n\"    -o passive             communicate over stdin and stdout bypassing network\\n\"\n\"    -o disable_hardlink    link(2) will return with errno set to ENOSYS\\n\"\n\"    -o transform_symlinks  transform absolute symlinks to relative\\n\"\n\"    -o follow_symlinks     follow symlinks on the server\\n\"\n\"    -o no_check_root       don't check for existence of 'dir' on server\\n\"\n\"    -o password_stdin      read password from stdin (only for pam_mount!)\\n\"\n\"    -o max_conns=N         open parallel SSH connections\\n\"\n\"    -o vsock=CID:PORT      connect to the given vsock\\n\"\n\"    -o SSHOPT=VAL          ssh options (see man ssh_config)\\n\"\n\"\\n\"\n\"FUSE Options:\\n\",\nprogname);\n}\n\nstatic int is_ssh_opt(const char *arg)\n{\n\tif (arg[0] != '-') {\n\t\tunsigned arglen = strlen(arg);\n\t\tconst char **o;\n\t\tfor (o = ssh_opts; *o; o++) {\n\t\t\tunsigned olen = strlen(*o);\n\t\t\tif (arglen > olen && arg[olen] == '=' &&\n\t\t\t    strncasecmp(arg, *o, olen) == 0)\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int sshfs_opt_proc(void *data, const char *arg, int key,\n                          struct fuse_args *outargs)\n{\n\t(void) outargs; (void) data;\n\tchar *tmp;\n\n\tswitch (key) {\n\tcase FUSE_OPT_KEY_OPT:\n\t\tif (is_ssh_opt(arg)) {\n\t\t\ttmp = g_strdup_printf(\"-o%s\", arg);\n\t\t\tssh_add_arg(tmp);\n\t\t\tg_free(tmp);\n\t\t\treturn 0;\n\t\t}\n\t\t/* Pass through */\n\t\treturn 1;\n\n\tcase FUSE_OPT_KEY_NONOPT:\n\t\tif (!sshfs.host && strchr(arg, ':')) {\n\t\t\tsshfs.host = strdup(arg);\n\t\t\treturn 0;\n\t\t}\n\t\telse if (!sshfs.mountpoint) {\n#if defined(__CYGWIN__)\n\t\t\t/*\n\t\t\t * On FUSE for Cygwin the mountpoint may be a drive or directory.\n\t\t\t * Furthermore the mountpoint must NOT exist prior to mounting.\n\t\t\t * So we cannot use realpath(3).\n\t\t\t */\n\t\t\tif ((('A' <= arg[0] && arg[0] <= 'Z') || ('a' <= arg[0] && arg[0] <= 'z'))\n\t\t\t\t&& ':' == arg[1] && '\\0' == arg[2]) {\n\t\t\t\t/* drive: make a copy */\n\t\t\t\tsshfs.mountpoint = strdup(arg);\n\t\t\t} else {\n\t\t\t\t/* path: split into dirname, basename and check dirname */\n\t\t\t\tchar *dir;\n\t\t\t\tconst char *base;\n\t\t\t\tconst char *slash = strrchr(arg, '/');\n\t\t\t\tif (slash) {\n\t\t\t\t\tchar *tmp = strndup(arg, slash == arg ? 1 : slash - arg);\n\t\t\t\t\tdir = tmp ? realpath(tmp, NULL) : 0;\n\t\t\t\t\tbase = slash + 1;\n\t\t\t\t\tfree(tmp);\n\t\t\t\t} else {\n\t\t\t\t\tdir = realpath(\".\", NULL);\n\t\t\t\t\tbase = arg;\n\t\t\t\t}\n\t\t\t\tif (dir) {\n\t\t\t\t\tslash = '/' == dir[0] && '\\0' == dir[1] ? \"\" : \"/\";\n\t\t\t\t\tasprintf(&sshfs.mountpoint, \"%s%s%s\", dir, slash, base);\n\t\t\t\t\tfree(dir);\n\t\t\t\t}\n\t\t\t}\n#else\n\t\t\tint fd, len;\n\t\t\tif (sscanf(arg, \"/dev/fd/%u%n\", &fd, &len) == 1 &&\n\t\t\t    len == strlen(arg)) {\n\t\t\t\t/*\n\t\t\t\t * Allow /dev/fd/N unchanged; it can be\n\t\t\t\t * use for pre-mounting a generic fuse\n\t\t\t\t * mountpoint to later be completely\n\t\t\t\t * unprivileged with libfuse >= 3.3.0.\n\t\t\t\t */\n\t\t\t\tsshfs.mountpoint = strdup(arg);\n\t\t\t} else {\n\t\t\t\tsshfs.mountpoint = realpath(arg, NULL);\n#ifdef __APPLE__\n\t\t\t\tif (!sshfs.mountpoint) {\n\t\t\t\t\t/*\n\t\t\t\t\t * The mountpoint does not exist, yet.\n\t\t\t\t\t * macFUSE will try to create it before\n\t\t\t\t\t * mounting the volume.\n\t\t\t\t\t */\n\t\t\t\t\tsshfs.mountpoint = strdup(arg);\n\t\t\t\t}\n#endif\n\t\t\t}\n#endif\n\t\t\tif (!sshfs.mountpoint) {\n\t\t\t\tfprintf(stderr, \"sshfs: bad mount point `%s': %s\\n\",\n\t\t\t\t\targ, strerror(errno));\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t\tfprintf(stderr, \"sshfs: invalid argument `%s'\\n\", arg);\n\t\treturn -1;\n\n\n\tcase KEY_PORT:\n\t\ttmp = g_strdup_printf(\"-oPort=%s\", arg + 2);\n\t\tssh_add_arg(tmp);\n\t\tg_free(tmp);\n\t\treturn 0;\n\n\tcase KEY_COMPRESS:\n\t\tssh_add_arg(\"-oCompression=yes\");\n\t\treturn 0;\n\n\tcase KEY_CONFIGFILE:\n\t\ttmp = g_strdup_printf(\"-F%s\", arg + 2);\n\t\tssh_add_arg(tmp);\n\t\tg_free(tmp);\n\t\treturn 0;\n\n\tdefault:\n\t\tfprintf(stderr, \"internal error\\n\");\n\t\tabort();\n\t}\n}\n\nstatic int workaround_opt_proc(void *data, const char *arg, int key,\n\t\t\t       struct fuse_args *outargs)\n{\n\t(void) data; (void) key; (void) outargs;\n\tfprintf(stderr, \"unknown workaround: '%s'\\n\", arg);\n\treturn -1;\n}\n\nstatic int parse_workarounds(void)\n{\n\tint res;\n\t/* Need separate variables because literals are const\n\t   char */\n\tchar argv0[] = \"\";\n\tchar argv1[] = \"-o\";\n\tchar *argv[] = { argv0, argv1, sshfs.workarounds, NULL };\n\tstruct fuse_args args = FUSE_ARGS_INIT(3, argv);\n\tchar *s = sshfs.workarounds;\n\tif (!s)\n\t\treturn 0;\n\n\twhile ((s = strchr(s, ':')))\n\t\t*s = ',';\n\n\tres = fuse_opt_parse(&args, &sshfs, workaround_opts,\n\t\t\t     workaround_opt_proc);\n\tfuse_opt_free_args(&args);\n\n\treturn res;\n}\n\nstatic int read_password(void)\n{\n\tint size = getpagesize();\n\tint max_password = MIN(MAX_PASSWORD, size - 1);\n\tint n;\n\n\tsshfs.password = mmap(NULL, size, PROT_READ | PROT_WRITE,\n\t\t\t      MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED,\n\t\t\t      -1, 0);\n\tif (sshfs.password == MAP_FAILED) {\n\t\tperror(\"Failed to allocate locked page for password\");\n\t\treturn -1;\n\t}\n\tif (mlock(sshfs.password, size) != 0) {\n\t\tmemset(sshfs.password, 0, size);\n\t\tmunmap(sshfs.password, size);\n\t\tsshfs.password = NULL;\n\t\tperror(\"Failed to allocate locked page for password\");\n\t\treturn -1;\n\t}\n\n\t/* Don't use fgets() because password might stay in memory */\n\tfor (n = 0; n < max_password; n++) {\n\t\tint res;\n\n\t\tres = read(0, &sshfs.password[n], 1);\n\t\tif (res == -1) {\n\t\t\tperror(\"Reading password\");\n\t\t\treturn -1;\n\t\t}\n\t\tif (res == 0) {\n\t\t\tsshfs.password[n] = '\\n';\n\t\t\tbreak;\n\t\t}\n\t\tif (sshfs.password[n] == '\\n')\n\t\t\tbreak;\n\t}\n\tif (n == max_password) {\n\t\tfprintf(stderr, \"Password too long\\n\");\n\t\treturn -1;\n\t}\n\tsshfs.password[n+1] = '\\0';\n\tssh_add_arg(\"-oNumberOfPasswordPrompts=1\");\n\n\treturn 0;\n}\n\n// Behaves similarly to strtok(), but allows for the ' ' delimiter to be escaped\n// by '\\ '.\nstatic char *tokenize_on_space(char *str)\n{\n\tstatic char *pos = NULL;\n\tchar *start = NULL;\n\n\tif (str)\n\t\tpos = str;\n\n\tif (!pos)\n\t\treturn NULL;\n\n\t// trim any leading spaces\n\twhile (*pos == ' ')\n\t\tpos++;\n\n\tstart = pos;\n\n\twhile (pos && *pos != '\\0') {\n\t\t// break on space, but not on '\\ '\n\t\tif (*pos == ' ' && *(pos - 1) != '\\\\') {\n\t\t\tbreak;\n\t\t}\n\t\tpos++;\n\t}\n\n\tif (*pos == '\\0') {\n\t\tpos = NULL;\n\t}\n\telse {\n\t\t*pos = '\\0';\n\t\tpos++;\n\t}\n\n\treturn start;\n}\n\nstatic void set_ssh_command(void)\n{\n\tchar *token = NULL;\n\tint i = 0;\n\n\ttoken = tokenize_on_space(sshfs.ssh_command);\n\twhile (token != NULL) {\n\t\tif (i == 0) {\n\t\t\treplace_arg(&sshfs.ssh_args.argv[0], token);\n\t\t} else {\n\t\t\tif (fuse_opt_insert_arg(&sshfs.ssh_args, i, token) == -1)\n\t\t\t\t_exit(1);\n\t\t}\n\t\ti++;\n\n\t\ttoken = tokenize_on_space(NULL);\n\t}\n}\n\nstatic char *find_base_path(void)\n{\n\tchar *s = sshfs.host;\n\tchar *d = s;\n\n\tfor (; *s && *s != ':'; s++) {\n\t\tif (*s == '[') {\n\t\t\t/*\n\t\t\t * Handle IPv6 numerical address enclosed in square\n\t\t\t * brackets\n\t\t\t */\n\t\t\ts++;\n\t\t\tfor (; *s != ']'; s++) {\n\t\t\t\tif (!*s) {\n\t\t\t\t\tfprintf(stderr,\t\"missing ']' in hostname\\n\");\n\t\t\t\t\texit(1);\n\t\t\t\t}\n\t\t\t\t*d++ = *s;\n\t\t\t}\n\t\t} else {\n\t\t\t*d++ = *s;\n\t\t}\n\n\t}\n\t*d++ = '\\0';\n\ts++;\n\n\treturn s;\n}\n\nstatic char *fsname_escape_commas(char *fsnameold)\n{\n\tchar *fsname = g_malloc(strlen(fsnameold) * 2 + 1);\n\tchar *d = fsname;\n\tchar *s;\n\n\tfor (s = fsnameold; *s; s++) {\n\t\tif (*s == '\\\\' || *s == ',')\n\t\t\t*d++ = '\\\\';\n\t\t*d++ = *s;\n\t}\n\t*d = '\\0';\n\tg_free(fsnameold);\n\n\treturn fsname;\n}\n\nstatic int ssh_connect(void)\n{\n\tint res;\n\n\tres = processing_init();\n\tif (res == -1)\n\t\treturn -1;\n\n\tif (!sshfs.delay_connect) {\n\t\tif (connect_remote(&sshfs.conns[0]) == -1)\n\t\t\treturn -1;\n\n\t\tif (!sshfs.no_check_root &&\n\t\t    sftp_check_root(&sshfs.conns[0], sshfs.base_path) != 0)\n\t\t\treturn -1;\n\n\t}\n\treturn 0;\n}\n\n/* number of ':' separated fields in a passwd/group file that we care\n * about */\n#define IDMAP_FIELDS 3\n\n/* given a line from a uidmap or gidmap, parse out the name and id */\nstatic void parse_idmap_line(char *line, const char* filename,\n\t\tconst unsigned int lineno, uint32_t *ret_id, char **ret_name,\n\t\tconst int eof)\n{\n\t/* chomp off the trailing newline */\n\tchar *p = line;\n\tif ((p = strrchr(line, '\\n')))\n\t\t*p = '\\0';\n\telse if (!eof) {\n\t\tfprintf(stderr, \"%s:%u: line too long\\n\", filename, lineno);\n\t\texit(1);\n\t}\n\tchar *tokens[IDMAP_FIELDS];\n\tchar *tok;\n\tint i;\n\tfor (i = 0; (tok = strsep(&line, \":\")) && (i < IDMAP_FIELDS) ; i++) {\n\t\ttokens[i] = tok;\n\t}\n\n\tchar *name_tok, *id_tok;\n\tif (i == 2) {\n\t\t/* assume name:id format */\n\t\tname_tok = tokens[0];\n\t\tid_tok = tokens[1];\n\t} else if (i >= IDMAP_FIELDS) {\n\t\t/* assume passwd/group file format */\n\t\tname_tok = tokens[0];\n\t\tid_tok = tokens[2];\n\t} else {\n\t\tfprintf(stderr, \"%s:%u: unknown format\\n\", filename, lineno);\n\t\texit(1);\n\t}\n\n\terrno = 0;\n\tuint32_t remote_id = strtoul(id_tok, NULL, 10);\n\tif (errno) {\n\t\tfprintf(stderr, \"Invalid id number on line %u of '%s': %s\\n\",\n\t\t\t\tlineno, filename, strerror(errno));\n\t\texit(1);\n\t}\n\n\t*ret_name = strdup(name_tok);\n\t*ret_id = remote_id;\n}\n\n/* read a uidmap or gidmap */\nstatic void read_id_map(char *file, uint32_t *(*map_fn)(char *),\n\t\tconst char *name_id, GHashTable **idmap, GHashTable **r_idmap)\n{\n\t*idmap = g_hash_table_new(NULL, NULL);\n\t*r_idmap = g_hash_table_new(NULL, NULL);\n\tFILE *fp;\n\tchar line[LINE_MAX];\n\tunsigned int lineno = 0;\n\tuid_t local_uid = getuid();\n\n\tfp = fopen(file, \"r\");\n\tif (fp == NULL) {\n\t\tfprintf(stderr, \"failed to open '%s': %s\\n\",\n\t\t\t\tfile, strerror(errno));\n\t\texit(1);\n\t}\n\tstruct stat st;\n\tif (fstat(fileno(fp), &st) == -1) {\n\t\tfprintf(stderr, \"failed to stat '%s': %s\\n\", file,\n\t\t\t\tstrerror(errno));\n\t\texit(1);\n\t}\n\tif (st.st_uid != local_uid) {\n\t\tfprintf(stderr, \"'%s' is not owned by uid %lu\\n\", file,\n\t\t\t\t(unsigned long)local_uid);\n\t\texit(1);\n\t}\n\tif (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) {\n\t\tfprintf(stderr, \"'%s' is writable by other users\\n\", file);\n\t\texit(1);\n\t}\n\n\twhile (fgets(line, LINE_MAX, fp) != NULL) {\n\t\tlineno++;\n\t\tuint32_t remote_id;\n\t\tchar *name;\n\n\t\t/* skip blank lines */\n\t\tif (line[0] == '\\n' || line[0] == '\\0')\n\t\t\tcontinue;\n\n\t\tparse_idmap_line(line, file, lineno, &remote_id, &name, feof(fp));\n\n\t\tuint32_t *local_id = map_fn(name);\n\t\tif (local_id == NULL) {\n\t\t\t/* not found */\n\t\t\tDEBUG(\"%s(%u): no local %s\\n\", name, remote_id, name_id);\n\t\t\tfree(name);\n\t\t\tcontinue;\n\t\t}\n\n\t\tDEBUG(\"%s: remote %s %u => local %s %u\\n\",\n\t\t\t\tname, name_id, remote_id, name_id, *local_id);\n\t\tg_hash_table_insert(*idmap, GUINT_TO_POINTER(remote_id), GUINT_TO_POINTER(*local_id));\n\t\tg_hash_table_insert(*r_idmap, GUINT_TO_POINTER(*local_id), GUINT_TO_POINTER(remote_id));\n\t\tfree(name);\n\t\tfree(local_id);\n\t}\n\n\tif (fclose(fp) == EOF) {\n\t\tfprintf(stderr, \"failed to close '%s': %s\",\n\t\t\t\tfile, strerror(errno));\n\t\texit(1);\n\t}\n}\n\n/* given a username, return a pointer to its uid, or NULL if it doesn't\n * exist on this system */\nstatic uint32_t *username_to_uid(char *name)\n{\n\terrno = 0;\n\tstruct passwd *pw = getpwnam(name);\n\tif (pw == NULL) {\n\t\tif (errno == 0) {\n\t\t\t/* \"does not exist\" */\n\t\t\treturn NULL;\n\t\t}\n\t\tfprintf(stderr, \"Failed to look up user '%s': %s\\n\",\n\t\t\t\tname, strerror(errno));\n\t\texit(1);\n\t}\n\tuint32_t *r = malloc(sizeof(uint32_t));\n\tif (r == NULL) {\n\t\tfprintf(stderr, \"sshfs: memory allocation failed\\n\");\n\t\tabort();\n\t}\n\t*r = pw->pw_uid;\n\treturn r;\n}\n\n/* given a groupname, return a pointer to its gid, or NULL if it doesn't\n * exist on this system */\nstatic uint32_t *groupname_to_gid(char *name)\n{\n\terrno = 0;\n\tstruct group *gr = getgrnam(name);\n\tif (gr == NULL) {\n\t\tif (errno == 0) {\n\t\t\t/* \"does not exist\" */\n\t\t\treturn NULL;\n\t\t}\n\t\tfprintf(stderr, \"Failed to look up group '%s': %s\\n\",\n\t\t\t\tname, strerror(errno));\n\t\texit(1);\n\t}\n\tuint32_t *r = malloc(sizeof(uint32_t));\n\tif (r == NULL) {\n\t\tfprintf(stderr, \"sshfs: memory allocation failed\\n\");\n\t\tabort();\n\t}\n\t*r = gr->gr_gid;\n\treturn r;\n}\n\nstatic inline void load_uid_map(void)\n{\n\tread_id_map(sshfs.uid_file, &username_to_uid, \"uid\", &sshfs.uid_map, &sshfs.r_uid_map);\n}\n\nstatic inline void load_gid_map(void)\n{\n\tread_id_map(sshfs.gid_file, &groupname_to_gid, \"gid\", &sshfs.gid_map, &sshfs.r_gid_map);\n}\n\n#ifdef __APPLE__\nint main(int argc, char *argv[], __unused char *envp[], char **exec_path)\n#else\nint main(int argc, char *argv[])\n#endif\n{\n\tint res;\n\tstruct fuse_args args = FUSE_ARGS_INIT(argc, argv);\n\tchar *tmp;\n\tchar *fsname;\n\tconst char *sftp_server;\n\tstruct fuse *fuse;\n\tstruct fuse_session *se;\n\tint i;\n\n#ifdef __APPLE__\n\tif (!realpath(*exec_path, sshfs_program_path)) {\n\t\tmemset(sshfs_program_path, 0, PATH_MAX);\n\t}\n#endif /* __APPLE__ */\n\n#ifdef __APPLE__\n\tsshfs.blksize = 0;\n#else\n\tsshfs.blksize = 4096;\n#endif\n\t/* SFTP spec says all servers should allow at least 32k I/O */\n\tsshfs.max_read = 32768;\n\tsshfs.max_write = 32768;\n#ifdef __APPLE__\n\tsshfs.rename_workaround = 1;\n#else\n\tsshfs.rename_workaround = 0;\n#endif\n\tsshfs.renamexdev_workaround = 0;\n\tsshfs.truncate_workaround = 0;\n\tsshfs.buflimit_workaround = 0;\n\tsshfs.createmode_workaround = 0;\n\tsshfs.ssh_ver = 2;\n\tsshfs.progname = argv[0];\n\tsshfs.max_conns = 1;\n\tsshfs.ptyfd = -1;\n\tsshfs.dir_cache = 1;\n\tsshfs.show_help = 0;\n\tsshfs.show_version = 0;\n\tsshfs.singlethread = 0;\n\tsshfs.foreground = 0;\n\tsshfs.ptypassivefd = -1;\n\tsshfs.delay_connect = 0;\n\tsshfs.passive = 0;\n\tsshfs.detect_uid = 0;\n\tif (strcmp(IDMAP_DEFAULT, \"none\") == 0) {\n\t\tsshfs.idmap = IDMAP_NONE;\n\t} else if (strcmp(IDMAP_DEFAULT, \"user\") == 0) {\n\t\tsshfs.idmap = IDMAP_USER;\n\t} else {\n\t\tfprintf(stderr, \"bad idmap default value built into sshfs; \"\n\t\t    \"assuming none (bad logic in configure script?)\\n\");\n\t\tsshfs.idmap = IDMAP_NONE;\n\t}\n\tsshfs.nomap = NOMAP_ERROR;\n\tssh_add_arg(\"ssh\");\n\tssh_add_arg(\"-x\");\n\tssh_add_arg(\"-a\");\n\tssh_add_arg(\"-oClearAllForwardings=yes\");\n\n\tif (fuse_opt_parse(&args, &sshfs, sshfs_opts, sshfs_opt_proc) == -1 ||\n\t    parse_workarounds() == -1)\n\t\texit(1);\n\n\tif (sshfs.show_version) {\n\t\tprintf(\"SSHFS version %s\\n\", PACKAGE_VERSION);\n\t\tprintf(\"FUSE library version %s\\n\", fuse_pkgversion());\n#if !defined(__CYGWIN__)\n\t\tfuse_lowlevel_version();\n#endif\n\t\texit(0);\n\t}\n\n\tif (sshfs.show_help) {\n\t\tusage(args.argv[0]);\n\t\tfuse_lib_help(&args);\n\t\texit(0);\n\t} else if (!sshfs.host) {\n\t\tfprintf(stderr, \"missing host\\n\");\n\t\tfprintf(stderr, \"see `%s -h' for usage\\n\", argv[0]);\n\t\texit(1);\n\t} else if (!sshfs.mountpoint) {\n\t\tfprintf(stderr, \"error: no mountpoint specified\\n\");\n\t\tfprintf(stderr, \"see `%s -h' for usage\\n\", argv[0]);\n\t\texit(1);\n\t}\n\n\tif (sshfs.idmap == IDMAP_USER)\n\t\tsshfs.detect_uid = 1;\n\telse if (sshfs.idmap == IDMAP_FILE) {\n\t\tsshfs.uid_map = NULL;\n\t\tsshfs.gid_map = NULL;\n\t\tsshfs.r_uid_map = NULL;\n\t\tsshfs.r_gid_map = NULL;\n\t\tif (!sshfs.uid_file && !sshfs.gid_file) {\n\t\t\tfprintf(stderr, \"need a uidfile or gidfile with idmap=file\\n\");\n\t\t\texit(1);\n\t\t}\n\t\tif (sshfs.uid_file)\n\t\t\tload_uid_map();\n\t\tif (sshfs.gid_file)\n\t\t\tload_gid_map();\n\t}\n\tfree(sshfs.uid_file);\n\tfree(sshfs.gid_file);\n\n\tDEBUG(\"SSHFS version %s\\n\", PACKAGE_VERSION);\n\n\t/* Force sshfs to the foreground when using stdin+stdout */\n\tif (sshfs.passive)\n\t\tsshfs.foreground = 1;\n\n\n\tif (sshfs.passive && sshfs.password_stdin) {\n\t\tfprintf(stderr, \"the password_stdin and passive options cannot both be specified\\n\");\n\t\texit(1);\n\t}\n\n\tif (sshfs.password_stdin) {\n\t\tres = read_password();\n\t\tif (res == -1)\n\t\t\texit(1);\n\t}\n\n\tif (sshfs.debug)\n\t\tsshfs.foreground = 1;\n\n\tif (sshfs.buflimit_workaround)\n\t\t/* Work around buggy sftp-server in OpenSSH.  Without this on\n\t\t   a slow server a 10Mbyte buffer would fill up and the server\n\t\t   would abort */\n\t\tsshfs.max_outstanding_len = 8388608;\n\telse\n\t\tsshfs.max_outstanding_len = ~0;\n\n\tif (sshfs.max_conns > 1) {\n\t\tif (sshfs.buflimit_workaround) {\n\t\t\tfprintf(stderr, \"buflimit workaround is not supported with parallel connections\\n\");\n\t\t\texit(1);\n\t\t}\n\n\t\tif (sshfs.password_stdin) {\n\t\t\tfprintf(stderr, \"password_stdin option cannot be specified with parallel connections\\n\");\n\t\t\texit(1);\n\t\t}\n\n\t\tif (sshfs.passive) {\n\t\t\tfprintf(stderr, \"passive option cannot be specified with parallel connections\\n\");\n\t\t\texit(1);\n\t\t}\n\t} else if (sshfs.max_conns <= 0) {\n\t\tfprintf(stderr, \"value of max_conns option must be at least 1\\n\");\n\t\texit(1);\n\t}\n\n\tsshfs.conns = g_new0(struct conn, sshfs.max_conns);\n\tfor (i = 0; i < sshfs.max_conns; i++) {\n\t\tsshfs.conns[i].rfd = -1;\n\t\tsshfs.conns[i].wfd = -1;\n\t}\n\n\tfsname = g_strdup(sshfs.host);\n\tsshfs.base_path = g_strdup(find_base_path());\n\n\tif (sshfs.ssh_command)\n\t\tset_ssh_command();\n\n\ttmp = g_strdup_printf(\"-%i\", sshfs.ssh_ver);\n\tssh_add_arg(tmp);\n\tg_free(tmp);\n\tssh_add_arg(sshfs.host);\n\tif (sshfs.sftp_server)\n\t\tsftp_server = sshfs.sftp_server;\n\telse if (sshfs.ssh_ver == 1)\n\t\tsftp_server = SFTP_SERVER_PATH;\n\telse\n\t\tsftp_server = \"sftp\";\n\n\tif (sshfs.ssh_ver != 1 && strchr(sftp_server, '/') == NULL)\n\t\tssh_add_arg(\"-s\");\n\n\tssh_add_arg(sftp_server);\n\tfree(sshfs.sftp_server);\n\n\tres = cache_parse_options(&args);\n\tif (res == -1)\n\t\texit(1);\n\n\tsshfs.randseed = time(0);\n\n\tif (sshfs.max_read > 65536)\n\t\tsshfs.max_read = 65536;\n\tif (sshfs.max_write > 65536)\n\t\tsshfs.max_write = 65536;\n\n\tfsname = fsname_escape_commas(fsname);\n\ttmp = g_strdup_printf(\"-osubtype=sshfs,fsname=%s\", fsname);\n\tfuse_opt_insert_arg(&args, 1, tmp);\n\tg_free(tmp);\n\tg_free(fsname);\n\n\tif(sshfs.dir_cache)\n\t\tsshfs.op = cache_wrap(&sshfs_oper);\n\telse\n\t\tsshfs.op = &sshfs_oper;\n\tfuse = fuse_new(&args, sshfs.op,\n\t\t\tsizeof(struct fuse_operations), NULL);\n\tif(fuse == NULL)\n\t\texit(1);\n\tse = fuse_get_session(fuse);\n\tres = fuse_set_signal_handlers(se);\n\tif (res != 0) {\n\t\tfuse_destroy(fuse);\n\t\texit(1);\n\t}\n\n\tres = fuse_mount(fuse, sshfs.mountpoint);\n\tif (res != 0) {\n\t\tfuse_destroy(fuse);\n\t\texit(1);\n\t}\n\n#if !defined(__CYGWIN__)\n\tres = fcntl(fuse_session_fd(se), F_SETFD, FD_CLOEXEC);\n\tif (res == -1)\n\t\tperror(\"WARNING: failed to set FD_CLOEXEC on fuse device\");\n#endif\n\n\t/*\n\t * FIXME: trim $PATH so it doesn't contain anything inside the\n\t * mountpoint, which would deadlock.\n\t */\n\tres = ssh_connect();\n\tif (res == -1) {\n\t\tfuse_unmount(fuse);\n\t\tfuse_destroy(fuse);\n\t\texit(1);\n\t}\n\n\tres = fuse_daemonize(sshfs.foreground);\n\tif (res == -1) {\n\t\tfuse_unmount(fuse);\n\t\tfuse_destroy(fuse);\n\t\texit(1);\n\t}\n\n\tif (sshfs.singlethread)\n\t\tres = fuse_loop(fuse);\n\telse\n\t\tres = fuse_loop_mt(fuse, 0);\n\n\tif (res != 0)\n\t\tres = 1;\n\telse\n\t\tres = 0;\n\n\tfuse_remove_signal_handlers(se);\n\tfuse_unmount(fuse);\n\tfuse_destroy(fuse);\n\n\tif (sshfs.debug) {\n\t\tunsigned int avg_rtt = 0;\n\n\t\tif (sshfs.num_sent)\n\t\t\tavg_rtt = sshfs.total_rtt / sshfs.num_sent;\n\n\t\tDEBUG(\"\\n\"\n\t\t      \"sent:               %llu messages, %llu bytes\\n\"\n\t\t      \"received:           %llu messages, %llu bytes\\n\"\n\t\t      \"rtt min/max/avg:    %ums/%ums/%ums\\n\"\n\t\t      \"num connect:        %u\\n\",\n\t\t      (unsigned long long) sshfs.num_sent,\n\t\t      (unsigned long long) sshfs.bytes_sent,\n\t\t      (unsigned long long) sshfs.num_received,\n\t\t      (unsigned long long) sshfs.bytes_received,\n\t\t      sshfs.min_rtt, sshfs.max_rtt, avg_rtt,\n\t\t      sshfs.num_connect);\n\t}\n\n\tfuse_opt_free_args(&args);\n\tfuse_opt_free_args(&sshfs.ssh_args);\n\tfree(sshfs.directport);\n\n\treturn res;\n}\n"
  },
  {
    "path": "sshfs.rst",
    "content": "=======\n SSHFS\n=======\n\n---------------------------------------------\n filesystem client based on SSH\n---------------------------------------------\n\n:Manual section: 1\n:Manual group: User Commands\n\nSynopsis\n========\n\nTo mount a filesystem::\n\n   sshfs [user@]host:[dir] mountpoint [options]\n\nIf *host* is a numeric IPv6 address, it needs to be enclosed in square\nbrackets.\n\nTo unmount it::\n\n  fusermount3 -u mountpoint   # Linux\n  umount mountpoint           # OS X, FreeBSD\n\nDescription\n===========\n\nSSHFS allows you to mount a remote filesystem using SSH (more precisely, the SFTP\nsubsystem). Most SSH servers support and enable this SFTP access by default, so SSHFS is\nvery simple to use - there's nothing to do on the server-side.\n\nBy default, file permissions are ignored by SSHFS. Any user that can access the filesystem\nwill be able to perform any operation that the remote server permits - based on the\ncredentials that were used to connect to the server. If this is undesired, local\npermission checking can be enabled with ``-o default_permissions``.\n\nBy default, only the mounting user will be able to access the filesystem. Access for other\nusers can be enabled by passing ``-o allow_other``. In this case you most likely also\nwant to use ``-o default_permissions``.\n\nIt is recommended to run SSHFS as regular user (not as root).  For this to work the\nmountpoint must be owned by the user.  If username is omitted SSHFS will use the local\nusername. If the directory is omitted, SSHFS will mount the (remote) home directory.  If\nyou need to enter a password sshfs will ask for it (actually it just runs ssh which ask\nfor the password if needed).\n\n\nOptions\n=======\n\n\n-o opt,[opt...]\n   mount options, see below for details. A a variety of SSH options can\n   be given here as well, see the manual pages for *sftp(1)* and\n   *ssh_config(5)*.\n\n-h, --help\n   print help and exit.\n\n-V, --version\n   print version information and exit.\n\n-d, --debug\n   print debugging information.\n\n-p PORT\n   equivalent to '-o port=PORT'\n\n-f\n   do not daemonize, stay in foreground.\n\n-s\n   Single threaded operation.\n\n-C\n   equivalent to '-o compression=yes'\n\n-F ssh_configfile\n   specifies alternative ssh configuration file\n\n-1\n   equivalent to '-o ssh_protocol=1'\n\n-o reconnect\n   automatically reconnect to server if connection is\n   interrupted. Attempts to access files that were opened before the\n   reconnection will give errors and need to be re-opened.\n\n-o delay_connect\n   Don't immediately connect to server, wait until mountpoint is first\n   accessed.\n\n-o sshfs_sync\n   synchronous writes. This will slow things down, but may be useful\n   in some situations.\n\n-o no_readahead\n   Only read exactly the data that was requested, instead of\n   speculatively reading more to anticipate the next read request.\n\n-o sync_readdir\n   synchronous readdir. This will slow things down, but may be useful\n   in some situations.\n\n-o workaround=LIST\n   Enable the specified workaround. See the `Caveats` section below\n   for some additional information. Possible values are:\n\n   :rename: Emulate overwriting an existing file by deleting and\n        renaming.\n   :renamexdev: Make rename fail with EXDEV instead of the default EPERM\n        to allow moving files across remote filesystems.\n   :truncate: Work around servers that don't support truncate by\n        coping the whole file, truncating it locally, and sending it\n        back.\n   :fstat: Work around broken servers that don't support *fstat()* by\n           using *stat* instead.\n   :buflimit: Work around OpenSSH \"buffer fillup\" bug.\n   :createmode: Work around broken servers that produce an error when passing a\n                non-zero mode to create, by always passing a mode of 0.\n\n-o idmap=TYPE\n   How to map remote UID/GIDs to local values. Possible values are:\n\n   :none: no translation of the ID space (default).\n\n   :user: map the UID/GID of the remote user to UID/GID of the\n            mounting user.\n\n   :file: translate UIDs/GIDs based upon the contents of `--uidfile`\n            and `--gidfile`.\n\n-o uidfile=FILE\n   file containing ``username:uid`` mappings for `-o idmap=file`\n\n-o gidfile=FILE\n   file containing ``groupname:gid`` mappings for `-o idmap=file`\n\n-o nomap=TYPE\n   with idmap=file, how to handle missing mappings:\n\n   :ignore: don't do any re-mapping\n   :error:  return an error (default)\n\n-o ssh_command=CMD\n   execute CMD instead of 'ssh'\n\n-o ssh_protocol=N\n   ssh protocol to use (default: 2)\n\n-o sftp_server=SERV\n   path to sftp server or subsystem (default: sftp)\n\n-o directport=PORT\n   directly connect to PORT bypassing ssh\n\n-o vsock=CID:PORT\n   directly connect using a vsock to CID:PORT bypassing ssh\n\n-o passive\n   communicate over stdin and stdout bypassing network. Useful for\n   mounting local filesystem on the remote side.  An example using\n   dpipe command would be ``dpipe /usr/lib/openssh/sftp-server = ssh\n   RemoteHostname sshfs :/directory/to/be/shared ~/mnt/src -o passive``\n\n-o disable_hardlink\n   With this option set, attempts to call `link(2)` will fail with\n   error code ENOSYS.\n\n-o transform_symlinks\n   transform absolute symlinks on remote side to relative\n   symlinks. This means that if e.g. on the server side\n   ``/foo/bar/com`` is a symlink to ``/foo/blub``, SSHFS will\n   transform the link target to ``../blub`` on the client side.\n\n-o follow_symlinks\n   follow symlinks on the server, i.e. present them as regular\n   files on the client. If a symlink is dangling (i.e, the target does\n   not exist) the behavior depends on the remote server - the entry\n   may appear as a symlink on the client, or it may appear as a\n   regular file that cannot be accessed.\n\n-o no_check_root\n   don't check for existence of 'dir' on server\n\n-o password_stdin\n   read password from stdin (only for pam_mount!)\n\n-o dir_cache=BOOL\n   Enables (*yes*) or disables (*no*) the SSHFS directory cache.  The\n   directory cache holds the names of directory entries. Enabling it\n   allows `readdir(3)` system calls to be processed without network\n   access.\n\n-o dcache_max_size=N\n   sets the maximum size of the directory cache.\n\n-o dcache_timeout=N\n   sets timeout for directory cache in seconds.\n\n-o dcache_{stat,link,dir}_timeout=N\n   sets separate timeout for {attributes, symlinks, names} in  the\n   directory cache.\n\n-o dcache_clean_interval=N\n   sets the interval for automatic cleaning of the directory cache.\n\n-o dcache_min_clean_interval=N\n   sets the interval for forced cleaning of the directory cache\n   when full.\n\n-o direct_io\n   This option disables the use of page cache (file content cache) in\n   the kernel for this filesystem.\n   This has several affects:\n\n   1. Each read() or write() system call will initiate one or more read or\n      write operations, data will not be cached in the kernel.\n\n   2. The return value of the read() and write() system calls will correspond\n      to the return values of the read and write operations. This is useful\n      for example if the file size is not known in advance (before reading it).\n      e.g. /proc filesystem\n\n-o max_conns=N\n   sets the maximum number of simultaneous SSH connections\n   to use. Each connection is established with a separate SSH process.\n   The primary purpose of this feature is to improve the responsiveness of the\n   file system during large file transfers. When using more than once\n   connection, the *password_stdin* and *passive* options can not be\n   used, and the *buflimit* workaround is not supported.\n\nIn addition, SSHFS accepts several options common to all FUSE file\nsystems. These are described in the `mount.fuse` manpage (look\nfor \"general\", \"libfuse specific\", and \"high-level API\" options).\n\nCaveats / Workarounds\n=====================\n\nHardlinks\n~~~~~~~~~\n\nIf the SSH server supports the *hardlinks* extension, SSHFS will allow\nyou to create hardlinks. However, hardlinks will always appear as\nindividual files when seen through an SSHFS mount, i.e. they will\nappear to have different inodes and an *st_nlink* value of 1.\n\n\nRename\n~~~~~~\n\nSome SSH servers do not support atomically overwriting the destination\nwhen renaming a file. In this case you will get an error when you\nattempt to rename a file and the destination already exists. A\nworkaround is to first remove the destination file, and then do the\nrename. SSHFS can do this automatically if you call it with `-o\nworkaround=rename`. However, in this case it is still possible that\nsomeone (or something) recreates the destination file after SSHFS has\nremoved it, but before SSHFS had the time to rename the old file. In\nthis case, the rename will still fail.\n\n\nPermission denied when moving files across remote filesystems\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nMost SFTP servers return only a generic \"failure\" when failing to rename\nacross filesystem boundaries (EXDEV).  sshfs normally converts this generic\nfailure to a permission denied error (EPERM).  If the option ``-o\nworkaround=renamexdev`` is given, generic failures will be considered EXDEV\nerrors which will make programs like `mv(1)` attempt to actually move the\nfile after the failed rename.\n\n\nSSHFS hangs for no apparent reason\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn some cases, attempts to access the SSHFS mountpoint may freeze if\nno filesystem activity has occurred for some time. This is typically\ncaused by the SSH connection being dropped because of inactivity\nwithout SSHFS being informed about that. As a workaround, you can try\nto mount with ``-o ServerAliveInterval=15``. This will force the SSH\nconnection to stay alive even if you have no activity.\n\n\nSSHFS hangs after the connection was interrupted\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nBy default, network operations in SSHFS run without timeouts, mirroring the\ndefault behavior of SSH itself. As a consequence, if the connection to the\nremote host is interrupted (e.g. because a network cable was removed),\noperations on files or directories under the mountpoint will block until the\nconnection is either restored or closed altogether (e.g. manually).\nApplications that try to access such files or directories will generally appear\nto \"freeze\" when this happens.\n\nIf it is acceptable to discard data being read or written, a quick workaround\nis to kill the responsible ``sshfs`` process, which will make any blocking\noperations on the mounted filesystem error out and thereby \"unfreeze\" the\nrelevant applications. Note that force unmounting with ``fusermount -zu``, on\nthe other hand, does not help in this case and will leave read/write operations\nin the blocking state.\n\nFor a more automatic solution, one can use the ``-o ServerAliveInterval=15``\noption mentioned above, which will drop the connection after not receiving a\nresponse for 3 * 15 = 45 seconds from the remote host. By also supplying ``-o\nreconnect``, one can ensure that the connection is re-established as soon as\npossible afterwards. As before, this will naturally lead to loss of data that\nwas in the process of being read or written at the time when the connection was\ninterrupted.\n\n\nMounting from /etc/fstab\n========================\n\nTo mount an SSHFS filesystem from ``/etc/fstab``, simply use ``sshfs``\nas the file system type. (For backwards compatibility, you may also\nuse ``fuse.sshfs``).\n\n\nSee also\n========\n\nThe `mount.fuse(8)` manpage.\n\nGetting Help\n============\n\nIf you need help, please ask on the <fuse-sshfs@lists.sourceforge.net>\nmailing list (subscribe at\nhttps://lists.sourceforge.net/lists/listinfo/fuse-sshfs).\n\nPlease report any bugs on the GitHub issue tracker at\nhttps://github.com/libfuse/libfuse/issues.\n\n\nAuthors\n=======\n\nSSHFS is currently maintained by Nikolaus Rath <Nikolaus@rath.org>,\nand was created by Miklos Szeredi <miklos@szeredi.hu>.\n\nThis man page was originally written by Bartosz Fenski\n<fenio@debian.org> for the Debian GNU/Linux distribution (but it may\nbe used by others).\n"
  },
  {
    "path": "test/.gitignore",
    "content": "__pycache__/\n"
  },
  {
    "path": "test/appveyor-build.sh",
    "content": "#!/bin/bash\nset -e\n\nmachine=$(uname -m)\nmkdir \"build-$machine\"\ncd \"build-$machine\"\nmeson ..\nninja\n"
  },
  {
    "path": "test/conftest.py",
    "content": "import sys\nimport pytest\nimport time\nimport re\n\n# If a test fails, wait a moment before retrieving the captured stdout/stderr.\n# When using a server process, this makes sure that we capture any potential\n# output of the server that comes *after* a test has failed. For example, if a\n# request handler raises an exception, the server first signals an error to\n# FUSE (causing the test to fail), and then logs the exception. Without the\n# extra delay, the exception will go into nowhere.\n\n\n@pytest.hookimpl(hookwrapper=True)\ndef pytest_pyfunc_call(pyfuncitem):\n    outcome = yield\n    failed = outcome.excinfo is not None\n    if failed:\n        time.sleep(1)\n\n\n@pytest.fixture()\ndef pass_capfd(request, capfd):\n    \"\"\"Provide capfd object to UnitTest instances\"\"\"\n    request.instance.capfd = capfd\n\n\ndef check_test_output(capfd):\n    (stdout, stderr) = capfd.readouterr()\n\n    # Write back what we've read (so that it will still be printed.\n    sys.stdout.write(stdout)\n    sys.stderr.write(stderr)\n\n    # Strip out false positives\n    for (pattern, flags, count) in capfd.false_positives:\n        cp = re.compile(pattern, flags)\n        (stdout, cnt) = cp.subn(\"\", stdout, count=count)\n        if count == 0 or count - cnt > 0:\n            stderr = cp.sub(\"\", stderr, count=count - cnt)\n\n    patterns = [\n        r\"\\b{}\\b\".format(x)\n        for x in (\n            \"exception\",\n            \"error\",\n            \"warning\",\n            \"fatal\",\n            \"traceback\",\n            \"fault\",\n            \"crash(?:ed)?\",\n            \"abort(?:ed)\",\n            \"uninitiali[zs]ed\",\n        )\n    ]\n    patterns += [\"^==[0-9]+== \"]\n    for pattern in patterns:\n        cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE)\n        hit = cp.search(stderr)\n        if hit:\n            raise AssertionError(\n                'Suspicious output to stderr (matched \"%s\")' % hit.group(0)\n            )\n        hit = cp.search(stdout)\n        if hit:\n            raise AssertionError(\n                'Suspicious output to stdout (matched \"%s\")' % hit.group(0)\n            )\n\n\ndef register_output(self, pattern, count=1, flags=re.MULTILINE):\n    \"\"\"Register *pattern* as false positive for output checking\n\n    This prevents the test from failing because the output otherwise\n    appears suspicious.\n    \"\"\"\n\n    self.false_positives.append((pattern, flags, count))\n\n\n# This is a terrible hack that allows us to access the fixtures from the\n# pytest_runtest_call hook. Among a lot of other hidden assumptions, it probably\n# relies on tests running sequential (i.e., don't dare to use e.g. the xdist\n# plugin)\ncurrent_capfd = None\n\n\n@pytest.fixture(autouse=True)\ndef save_cap_fixtures(request, capfd):\n    global current_capfd\n    capfd.false_positives = []\n\n    # Monkeypatch in a function to register false positives\n    type(capfd).register_output = register_output\n\n    if request.config.getoption(\"capture\") == \"no\":\n        capfd = None\n    current_capfd = capfd\n    bak = current_capfd\n    yield\n\n    # Try to catch problems with this hack (e.g. when running tests\n    # simultaneously)\n    assert bak is current_capfd\n    current_capfd = None\n\n\n@pytest.hookimpl(trylast=True)\ndef pytest_runtest_call(item):\n    capfd = current_capfd\n    if capfd is not None:\n        check_test_output(capfd)\n"
  },
  {
    "path": "test/lint.sh",
    "content": "#!/bin/bash\nset -e\npip3 install --user pre-commit\npre-commit run --all-files --show-diff-on-failure\n"
  },
  {
    "path": "test/lsan_suppress.txt",
    "content": "# Suppression file for address sanitizer.\n\n# There are some leaks in command line option parsing. They should be\n# fixed at some point, but are harmless since the consume just a small,\n# constant amount of memory and do not grow.\nleak:fuse_opt_parse\n\n\n# Leaks in fusermount3 are harmless as well (it's a short-lived\n# process) - but patches are welcome!\nleak:fusermount.c\n"
  },
  {
    "path": "test/meson.build",
    "content": "test_scripts = [ 'conftest.py', 'pytest.ini', 'test_sshfs.py',\n                 'util.py' ]\ncustom_target('test_scripts', input: test_scripts,\n              output: test_scripts, build_by_default: true,\n              command: ['cp', '-fPp',\n                        '@INPUT@', meson.current_build_dir() ])\n\n# Provide something helpful when running 'ninja test'\nwrong_cmd = executable('wrong_command', 'wrong_command.c',\n                       install: false)\ntest('wrong_cmd', wrong_cmd)\n"
  },
  {
    "path": "test/pytest.ini",
    "content": "[pytest]\naddopts = --verbose --assert=rewrite --tb=native -x -r a\n\nmarkers = uses_fuse: Mark to indicate that FUSE is available on the system running the test\n"
  },
  {
    "path": "test/test_sshfs.py",
    "content": "#!/usr/bin/env python3\n\nif __name__ == \"__main__\":\n    import pytest\n    import sys\n\n    sys.exit(pytest.main([__file__] + sys.argv[1:]))\n\nimport subprocess\nimport os\nimport sys\nimport pytest\nimport stat\nimport shutil\nimport filecmp\nimport errno\nfrom tempfile import NamedTemporaryFile\nfrom util import (\n    wait_for_mount,\n    umount,\n    cleanup,\n    base_cmdline,\n    basename,\n    fuse_test_marker,\n    safe_sleep,\n    os_create,\n    os_open,\n)\nfrom os.path import join as pjoin\n\nTEST_FILE = __file__\n\npytestmark = fuse_test_marker()\n\nwith open(TEST_FILE, \"rb\") as fh:\n    TEST_DATA = fh.read()\n\n\ndef name_generator(__ctr=[0]) -> str:\n    \"\"\"Generate a fresh filename on each call\"\"\"\n\n    __ctr[0] += 1\n    return f\"testfile_{__ctr[0]}\"\n\n\n@pytest.mark.parametrize(\n    \"debug\",\n    [pytest.param(False, id=\"debug=false\"), pytest.param(True, id=\"debug=true\")],\n)\n@pytest.mark.parametrize(\n    \"cache_timeout\",\n    [pytest.param(0, id=\"cache_timeout=0\"), pytest.param(1, id=\"cache_timeout=1\")],\n)\n@pytest.mark.parametrize(\n    \"sync_rd\",\n    [pytest.param(True, id=\"sync_rd=true\"), pytest.param(False, id=\"sync_rd=false\")],\n)\n@pytest.mark.parametrize(\n    \"multiconn\",\n    [\n        pytest.param(True, id=\"multiconn=true\"),\n        pytest.param(False, id=\"multiconn=false\"),\n    ],\n)\ndef test_sshfs(\n    tmpdir, debug: bool, cache_timeout: int, sync_rd: bool, multiconn: bool, capfd\n) -> None:\n\n    # Avoid false positives from debug messages\n    # if debug:\n    #    capfd.register_output(r'^   unique: [0-9]+, error: -[0-9]+ .+$',\n    #                          count=0)\n\n    # Avoid false positives from storing key for localhost\n    capfd.register_output(r\"^Warning: Permanently added 'localhost' .+\", count=0)\n\n    # Test if we can ssh into localhost without password\n    try:\n        res = subprocess.call(\n            [\n                \"ssh\",\n                \"-o\",\n                \"StrictHostKeyChecking=no\",\n                \"-o\",\n                \"KbdInteractiveAuthentication=no\",\n                \"-o\",\n                \"ChallengeResponseAuthentication=no\",\n                \"-o\",\n                \"PasswordAuthentication=no\",\n                \"localhost\",\n                \"--\",\n                \"true\",\n            ],\n            stdin=subprocess.DEVNULL,\n            timeout=10,\n        )\n    except subprocess.TimeoutExpired:\n        res = 1\n    if res != 0:\n        pytest.fail(\"Unable to ssh into localhost without password prompt.\")\n\n    mnt_dir = str(tmpdir.mkdir(\"mnt\"))\n    src_dir = str(tmpdir.mkdir(\"src\"))\n\n    cmdline = base_cmdline + [\n        pjoin(basename, \"sshfs\"),\n        \"-f\",\n        f\"localhost:{src_dir}\",\n        mnt_dir,\n    ]\n    if debug:\n        cmdline += [\"-o\", \"sshfs_debug\"]\n\n    if sync_rd:\n        cmdline += [\"-o\", \"sync_readdir\"]\n\n    # SSHFS Cache\n    if cache_timeout == 0:\n        cmdline += [\"-o\", \"dir_cache=no\"]\n    else:\n        cmdline += [\"-o\", f\"dcache_timeout={cache_timeout}\", \"-o\", \"dir_cache=yes\"]\n\n    # FUSE Cache\n    cmdline += [\"-o\", \"entry_timeout=0\", \"-o\", \"attr_timeout=0\"]\n\n    if multiconn:\n        cmdline += [\"-o\", \"max_conns=3\"]\n\n    new_env = dict(os.environ)  # copy, don't modify\n\n    # Abort on warnings from glib\n    new_env[\"G_DEBUG\"] = \"fatal-warnings\"\n\n    mount_process = subprocess.Popen(cmdline, env=new_env)\n    try:\n        wait_for_mount(mount_process, mnt_dir)\n\n        tst_statvfs(mnt_dir)\n        tst_readdir(src_dir, mnt_dir)\n        tst_open_read(src_dir, mnt_dir)\n        tst_open_write(src_dir, mnt_dir)\n        tst_append(src_dir, mnt_dir)\n        tst_seek(src_dir, mnt_dir)\n        tst_create(mnt_dir)\n        tst_passthrough(src_dir, mnt_dir, cache_timeout)\n        tst_mkdir(mnt_dir)\n        tst_rmdir(src_dir, mnt_dir, cache_timeout)\n        tst_unlink(src_dir, mnt_dir, cache_timeout)\n        tst_symlink(mnt_dir)\n        if os.getuid() == 0:\n            tst_chown(mnt_dir)\n\n        # SSHFS only supports one second resolution when setting\n        # file timestamps.\n        tst_utimens(mnt_dir, tol=1)\n        tst_utimens_now(mnt_dir)\n\n        tst_link(mnt_dir, cache_timeout)\n        tst_truncate_path(mnt_dir)\n        tst_truncate_fd(mnt_dir)\n        tst_open_unlink(mnt_dir)\n    except Exception as exc:\n        cleanup(mount_process, mnt_dir)\n        raise exc\n    else:\n        umount(mount_process, mnt_dir)\n\n\ndef tst_unlink(src_dir, mnt_dir, cache_timeout):\n    name = name_generator()\n    fullname = mnt_dir + \"/\" + name\n    with open(pjoin(src_dir, name), \"wb\") as fh:\n        fh.write(b\"hello\")\n    if cache_timeout:\n        safe_sleep(cache_timeout + 1)\n    assert name in os.listdir(mnt_dir)\n    os.unlink(fullname)\n    with pytest.raises(OSError) as exc_info:\n        os.stat(fullname)\n    assert exc_info.value.errno == errno.ENOENT\n    assert name not in os.listdir(mnt_dir)\n    assert name not in os.listdir(src_dir)\n\n\ndef tst_mkdir(mnt_dir):\n    dirname = name_generator()\n    fullname = mnt_dir + \"/\" + dirname\n    os.mkdir(fullname)\n    fstat = os.stat(fullname)\n    assert stat.S_ISDIR(fstat.st_mode)\n    assert os.listdir(fullname) == []\n    assert fstat.st_nlink in (1, 2)\n    assert dirname in os.listdir(mnt_dir)\n\n\ndef tst_rmdir(src_dir, mnt_dir, cache_timeout):\n    name = name_generator()\n    fullname = mnt_dir + \"/\" + name\n    os.mkdir(pjoin(src_dir, name))\n    if cache_timeout:\n        safe_sleep(cache_timeout + 1)\n    assert name in os.listdir(mnt_dir)\n    os.rmdir(fullname)\n    with pytest.raises(OSError) as exc_info:\n        os.stat(fullname)\n    assert exc_info.value.errno == errno.ENOENT\n    assert name not in os.listdir(mnt_dir)\n    assert name not in os.listdir(src_dir)\n\n\ndef tst_symlink(mnt_dir):\n    linkname = name_generator()\n    fullname = mnt_dir + \"/\" + linkname\n    os.symlink(\"/imaginary/dest\", fullname)\n    fstat = os.lstat(fullname)\n    assert stat.S_ISLNK(fstat.st_mode)\n    assert os.readlink(fullname) == \"/imaginary/dest\"\n    assert fstat.st_nlink == 1\n    assert linkname in os.listdir(mnt_dir)\n\n\ndef tst_create(mnt_dir):\n    name = name_generator()\n    fullname = pjoin(mnt_dir, name)\n    with pytest.raises(OSError) as exc_info:\n        os.stat(fullname)\n    assert exc_info.value.errno == errno.ENOENT\n    assert name not in os.listdir(mnt_dir)\n\n    fd = os.open(fullname, os.O_CREAT | os.O_RDWR)\n    os.close(fd)\n\n    assert name in os.listdir(mnt_dir)\n    fstat = os.lstat(fullname)\n    assert stat.S_ISREG(fstat.st_mode)\n    assert fstat.st_nlink == 1\n    assert fstat.st_size == 0\n\n\ndef tst_chown(mnt_dir):\n    filename = pjoin(mnt_dir, name_generator())\n    os.mkdir(filename)\n    fstat = os.lstat(filename)\n    uid = fstat.st_uid\n    gid = fstat.st_gid\n\n    uid_new = uid + 1\n    os.chown(filename, uid_new, -1)\n    fstat = os.lstat(filename)\n    assert fstat.st_uid == uid_new\n    assert fstat.st_gid == gid\n\n    gid_new = gid + 1\n    os.chown(filename, -1, gid_new)\n    fstat = os.lstat(filename)\n    assert fstat.st_uid == uid_new\n    assert fstat.st_gid == gid_new\n\n\ndef tst_open_read(src_dir, mnt_dir):\n    name = name_generator()\n    with open(pjoin(src_dir, name), \"wb\") as fh_out, open(TEST_FILE, \"rb\") as fh_in:\n        shutil.copyfileobj(fh_in, fh_out)\n\n    assert filecmp.cmp(pjoin(mnt_dir, name), TEST_FILE, False)\n\n\ndef tst_open_write(src_dir, mnt_dir):\n    name = name_generator()\n    fd = os.open(pjoin(src_dir, name), os.O_CREAT | os.O_RDWR)\n    os.close(fd)\n    fullname = pjoin(mnt_dir, name)\n    with open(fullname, \"wb\") as fh_out, open(TEST_FILE, \"rb\") as fh_in:\n        shutil.copyfileobj(fh_in, fh_out)\n\n    assert filecmp.cmp(fullname, TEST_FILE, False)\n\n\ndef tst_append(src_dir, mnt_dir):\n    name = name_generator()\n    os_create(pjoin(src_dir, name))\n    fullname = pjoin(mnt_dir, name)\n    with os_open(fullname, os.O_WRONLY) as fd:\n        os.write(fd, b\"foo\\n\")\n    with os_open(fullname, os.O_WRONLY | os.O_APPEND) as fd:\n        os.write(fd, b\"bar\\n\")\n\n    with open(fullname, \"rb\") as fh:\n        assert fh.read() == b\"foo\\nbar\\n\"\n\n\ndef tst_seek(src_dir, mnt_dir):\n    name = name_generator()\n    os_create(pjoin(src_dir, name))\n    fullname = pjoin(mnt_dir, name)\n    with os_open(fullname, os.O_WRONLY) as fd:\n        os.lseek(fd, 1, os.SEEK_SET)\n        os.write(fd, b\"foobar\\n\")\n    with os_open(fullname, os.O_WRONLY) as fd:\n        os.lseek(fd, 4, os.SEEK_SET)\n        os.write(fd, b\"com\")\n\n    with open(fullname, \"rb\") as fh:\n        assert fh.read() == b\"\\0foocom\\n\"\n\n\ndef tst_open_unlink(mnt_dir):\n    name = pjoin(mnt_dir, name_generator())\n    data1 = b\"foo\"\n    data2 = b\"bar\"\n    fullname = pjoin(mnt_dir, name)\n    with open(fullname, \"wb+\", buffering=0) as fh:\n        fh.write(data1)\n        os.unlink(fullname)\n        with pytest.raises(OSError) as exc_info:\n            os.stat(fullname)\n            assert exc_info.value.errno == errno.ENOENT\n        assert name not in os.listdir(mnt_dir)\n        fh.write(data2)\n        fh.seek(0)\n        assert fh.read() == data1 + data2\n\n\ndef tst_statvfs(mnt_dir):\n    os.statvfs(mnt_dir)\n\n\ndef tst_link(mnt_dir, cache_timeout):\n    name1 = pjoin(mnt_dir, name_generator())\n    name2 = pjoin(mnt_dir, name_generator())\n    shutil.copyfile(TEST_FILE, name1)\n    assert filecmp.cmp(name1, TEST_FILE, False)\n\n    fstat1 = os.lstat(name1)\n    assert fstat1.st_nlink == 1\n\n    os.link(name1, name2)\n\n    # The link operation changes st_ctime, and if we're unlucky\n    # the kernel will keep the old value cached for name1, and\n    # retrieve the new value for name2 (at least, this is the only\n    # way I can explain the test failure). To avoid this problem,\n    # we need to wait until the cached value has expired.\n    if cache_timeout:\n        safe_sleep(cache_timeout)\n\n    fstat1 = os.lstat(name1)\n    fstat2 = os.lstat(name2)\n    for attr in (\n        \"st_mode\",\n        \"st_dev\",\n        \"st_uid\",\n        \"st_gid\",\n        \"st_size\",\n        \"st_atime\",\n        \"st_mtime\",\n        \"st_ctime\",\n    ):\n        assert getattr(fstat1, attr) == getattr(fstat2, attr)\n    assert os.path.basename(name2) in os.listdir(mnt_dir)\n    assert filecmp.cmp(name1, name2, False)\n\n    os.unlink(name2)\n\n    assert os.path.basename(name2) not in os.listdir(mnt_dir)\n    with pytest.raises(FileNotFoundError):\n        os.lstat(name2)\n\n    os.unlink(name1)\n\n\ndef tst_readdir(src_dir, mnt_dir):\n    newdir = name_generator()\n    src_newdir = pjoin(src_dir, newdir)\n    mnt_newdir = pjoin(mnt_dir, newdir)\n    file_ = src_newdir + \"/\" + name_generator()\n    subdir = src_newdir + \"/\" + name_generator()\n    subfile = subdir + \"/\" + name_generator()\n\n    os.mkdir(src_newdir)\n    shutil.copyfile(TEST_FILE, file_)\n    os.mkdir(subdir)\n    shutil.copyfile(TEST_FILE, subfile)\n\n    listdir_is = os.listdir(mnt_newdir)\n    listdir_is.sort()\n    listdir_should = [os.path.basename(file_), os.path.basename(subdir)]\n    listdir_should.sort()\n    assert listdir_is == listdir_should\n\n    os.unlink(file_)\n    os.unlink(subfile)\n    os.rmdir(subdir)\n    os.rmdir(src_newdir)\n\n\ndef tst_truncate_path(mnt_dir):\n    assert len(TEST_DATA) > 1024\n\n    filename = pjoin(mnt_dir, name_generator())\n    with open(filename, \"wb\") as fh:\n        fh.write(TEST_DATA)\n\n    fstat = os.stat(filename)\n    size = fstat.st_size\n    assert size == len(TEST_DATA)\n\n    # Add zeros at the end\n    os.truncate(filename, size + 1024)\n    assert os.stat(filename).st_size == size + 1024\n    with open(filename, \"rb\") as fh:\n        assert fh.read(size) == TEST_DATA\n        assert fh.read(1025) == b\"\\0\" * 1024\n\n    # Truncate data\n    os.truncate(filename, size - 1024)\n    assert os.stat(filename).st_size == size - 1024\n    with open(filename, \"rb\") as fh:\n        assert fh.read(size) == TEST_DATA[: size - 1024]\n\n    os.unlink(filename)\n\n\ndef tst_truncate_fd(mnt_dir):\n    assert len(TEST_DATA) > 1024\n    with NamedTemporaryFile(\"w+b\", 0, dir=mnt_dir) as fh:\n        fd = fh.fileno()\n        fh.write(TEST_DATA)\n        fstat = os.fstat(fd)\n        size = fstat.st_size\n        assert size == len(TEST_DATA)\n\n        # Add zeros at the end\n        os.ftruncate(fd, size + 1024)\n        assert os.fstat(fd).st_size == size + 1024\n        fh.seek(0)\n        assert fh.read(size) == TEST_DATA\n        assert fh.read(1025) == b\"\\0\" * 1024\n\n        # Truncate data\n        os.ftruncate(fd, size - 1024)\n        assert os.fstat(fd).st_size == size - 1024\n        fh.seek(0)\n        assert fh.read(size) == TEST_DATA[: size - 1024]\n\n\ndef tst_utimens(mnt_dir, tol=0):\n    filename = pjoin(mnt_dir, name_generator())\n    os.mkdir(filename)\n    fstat = os.lstat(filename)\n\n    atime = fstat.st_atime + 42.28\n    mtime = fstat.st_mtime - 42.23\n    if sys.version_info < (3, 3):\n        os.utime(filename, (atime, mtime))\n    else:\n        atime_ns = fstat.st_atime_ns + int(42.28 * 1e9)\n        mtime_ns = fstat.st_mtime_ns - int(42.23 * 1e9)\n        os.utime(filename, None, ns=(atime_ns, mtime_ns))\n\n    fstat = os.lstat(filename)\n\n    assert abs(fstat.st_atime - atime) < tol\n    assert abs(fstat.st_mtime - mtime) < tol\n    if sys.version_info >= (3, 3):\n        assert abs(fstat.st_atime_ns - atime_ns) < tol * 1e9\n        assert abs(fstat.st_mtime_ns - mtime_ns) < tol * 1e9\n\n\ndef tst_utimens_now(mnt_dir):\n    fullname = pjoin(mnt_dir, name_generator())\n\n    fd = os.open(fullname, os.O_CREAT | os.O_RDWR)\n    os.close(fd)\n    os.utime(fullname, None)\n\n    fstat = os.lstat(fullname)\n    # We should get now-timestamps\n    assert fstat.st_atime != 0\n    assert fstat.st_mtime != 0\n\n\ndef tst_passthrough(src_dir, mnt_dir, cache_timeout):\n    name = name_generator()\n    src_name = pjoin(src_dir, name)\n    mnt_name = pjoin(src_dir, name)\n    assert name not in os.listdir(src_dir)\n    assert name not in os.listdir(mnt_dir)\n    with open(src_name, \"w\") as fh:\n        fh.write(\"Hello, world\")\n    assert name in os.listdir(src_dir)\n    if cache_timeout:\n        safe_sleep(cache_timeout + 1)\n    assert name in os.listdir(mnt_dir)\n    assert os.stat(src_name) == os.stat(mnt_name)\n\n    name = name_generator()\n    src_name = pjoin(src_dir, name)\n    mnt_name = pjoin(src_dir, name)\n    assert name not in os.listdir(src_dir)\n    assert name not in os.listdir(mnt_dir)\n    with open(mnt_name, \"w\") as fh:\n        fh.write(\"Hello, world\")\n    assert name in os.listdir(src_dir)\n    if cache_timeout:\n        safe_sleep(cache_timeout + 1)\n    assert name in os.listdir(mnt_dir)\n    assert os.stat(src_name) == os.stat(mnt_name)\n"
  },
  {
    "path": "test/travis-build.sh",
    "content": "#!/bin/bash\n\nset -e\n\n# Disable leak checking for now, there are some issues (or false positives)\n# that we still need to fix\nexport ASAN_OPTIONS=\"detect_leaks=0\"\n\nexport LSAN_OPTIONS=\"suppressions=${PWD}/test/lsan_suppress.txt\"\nexport CC\n\nTEST_CMD=\"python3 -m pytest --maxfail=99 test/\"\n\n# Standard build with Valgrind\nfor CC in gcc clang; do\n    (\n    mkdir \"build-${CC}\"; cd \"build-${CC}\"\n    if [ \"${CC}\" == 'gcc-6' ]; then\n        build_opts='-D b_lundef=false'\n    else\n        build_opts=''\n    fi\n    # shellcheck disable=SC2086\n    meson -D werror=true ${build_opts} ../\n    ninja\n\n    TEST_WITH_VALGRIND=true ${TEST_CMD}\n    )\ndone\n(cd \"build-${CC}\"; sudo ninja install)\n\n# Sanitized build\nCC=clang\nfor san in undefined address; do\n    (\n    mkdir \"build-${san}\"\n    cd \"build-${san}\"\n    # b_lundef=false is required to work around clang\n    # bug, cf. https://groups.google.com/forum/#!topic/mesonbuild/tgEdAXIIdC4\n    meson -D b_sanitize=${san} -D b_lundef=false -D werror=true ..\n    ninja\n    ${TEST_CMD}\n    sudo ninja install\n    )\ndone\n"
  },
  {
    "path": "test/travis-install.sh",
    "content": "#!/bin/sh\n\nset -e\n\n# Install fuse\nwget https://github.com/libfuse/libfuse/archive/master.zip\nunzip master.zip\ncd libfuse-master\nmkdir build\ncd build\nmeson ..\nninja\nsudo ninja install\ntest -e /usr/local/lib/pkgconfig || sudo mkdir /usr/local/lib/pkgconfig\nsudo mv /usr/local/lib/*/pkgconfig/* /usr/local/lib/pkgconfig/\nprintf '%s\\n' /usr/local/lib/*-linux-gnu | sudo tee /etc/ld.so.conf.d/usrlocal.conf\nsudo ldconfig\n\n# Setup ssh\nssh-keygen -b 1024 -t rsa -f ~/.ssh/id_rsa -P ''\ncat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys\nchmod 600 ~/.ssh/authorized_keys\nssh -o \"StrictHostKeyChecking=no\" localhost echo \"SSH connection succeeded\"\n"
  },
  {
    "path": "test/util.py",
    "content": "#!/usr/bin/env python3\nimport subprocess\nimport pytest\nimport os\nimport stat\nimport time\nfrom os.path import join as pjoin\nfrom contextlib import contextmanager\n\nbasename = pjoin(os.path.dirname(__file__), \"..\")\n\n\ndef os_create(name):\n    os.close(os.open(name, os.O_CREAT | os.O_RDWR))\n\n\n@contextmanager\ndef os_open(name, flags):\n    fd = os.open(name, flags)\n    try:\n        yield fd\n    finally:\n        os.close(fd)\n\n\ndef wait_for_mount(mount_process, mnt_dir, test_fn=os.path.ismount):\n    elapsed = 0\n    while elapsed < 30:\n        if test_fn(mnt_dir):\n            return True\n        if mount_process.poll() is not None:\n            pytest.fail(\"file system process terminated prematurely\")\n        time.sleep(0.1)\n        elapsed += 0.1\n    pytest.fail(\"mountpoint failed to come up\")\n\n\ndef cleanup(mount_process, mnt_dir):\n    subprocess.call(\n        [\"fusermount\", \"-z\", \"-u\", mnt_dir],\n        stdout=subprocess.DEVNULL,\n        stderr=subprocess.STDOUT,\n    )\n    mount_process.terminate()\n    try:\n        mount_process.wait(1)\n    except subprocess.TimeoutExpired:\n        mount_process.kill()\n\n\ndef umount(mount_process, mnt_dir):\n    subprocess.check_call([\"fusermount3\", \"-z\", \"-u\", mnt_dir])\n    assert not os.path.ismount(mnt_dir)\n\n    # Give mount process a little while to terminate. Popen.wait(timeout)\n    # was only added in 3.3...\n    elapsed = 0\n    while elapsed < 30:\n        code = mount_process.poll()\n        if code is not None:\n            if code == 0:\n                return\n            pytest.fail(f\"file system process terminated with code {code}\")\n        time.sleep(0.1)\n        elapsed += 0.1\n    pytest.fail(\"mount process did not terminate\")\n\n\ndef safe_sleep(secs):\n    \"\"\"Like time.sleep(), but sleep for at least *secs*\n\n    `time.sleep` may sleep less than the given period if a signal is\n    received. This function ensures that we sleep for at least the\n    desired time.\n    \"\"\"\n\n    now = time.time()\n    end = now + secs\n    while now < end:\n        time.sleep(end - now)\n        now = time.time()\n\n\ndef fuse_test_marker():\n    \"\"\"Return a pytest.marker that indicates FUSE availability\n\n    If system/user/environment does not support FUSE, return\n    a `pytest.mark.skip` object with more details. If FUSE is\n    supported, return `pytest.mark.uses_fuse()`.\n    \"\"\"\n\n    def skip(reason: str):\n        return pytest.mark.skip(reason=reason)\n\n    with subprocess.Popen(\n        [\"which\", \"fusermount\"], stdout=subprocess.PIPE, universal_newlines=True\n    ) as which:\n        fusermount_path = which.communicate()[0].strip()\n\n    if not fusermount_path or which.returncode != 0:\n        return skip(\"Can't find fusermount executable\")\n\n    if not os.path.exists(\"/dev/fuse\"):\n        return skip(\"FUSE kernel module does not seem to be loaded\")\n\n    if os.getuid() == 0:\n        return pytest.mark.uses_fuse()\n\n    mode = os.stat(fusermount_path).st_mode\n    if mode & stat.S_ISUID == 0:\n        return skip(\"fusermount executable not setuid, and we are not root.\")\n\n    try:\n        fd = os.open(\"/dev/fuse\", os.O_RDWR)\n    except OSError as exc:\n        return skip(f\"Unable to open /dev/fuse: {exc.strerror}\")\n    else:\n        os.close(fd)\n\n    return pytest.mark.uses_fuse()\n\n\n# Use valgrind if requested\nif os.environ.get(\"TEST_WITH_VALGRIND\", \"no\").lower().strip() not in (\n    \"no\",\n    \"false\",\n    \"0\",\n):\n    base_cmdline = [\"valgrind\", \"-q\", \"--\"]\nelse:\n    base_cmdline = []\n"
  },
  {
    "path": "test/wrong_command.c",
    "content": "#include <stdio.h>\n\nint main(void) {\n\tfprintf(stderr, \"\\x1B[31m\\e[1m\"\n\t\t\"This is not the command you are looking for.\\n\"\n\t\t\"You probably want to run 'python3 -m pytest test/' instead\"\n\t\t\"\\e[0m\\n\");\n\treturn 1;\n}\n"
  },
  {
    "path": "utils/install_helper.sh",
    "content": "#!/bin/sh\n#\n# Don't call this script. It is used internally by the Meson\n# build system. Thank you for your cooperation.\n#\n\nset -e\n\nbindir=\"$2\"\nsbindir=\"$1\"\nprefix=\"${MESON_INSTALL_DESTDIR_PREFIX}\"\n\nmkdir -p \"${prefix}/${sbindir}\"\n\nln -svf --relative \"${prefix}/${bindir}/sshfs\" \\\n   \"${prefix}/${sbindir}/mount.sshfs\"\n\nln -svf --relative \"${prefix}/${bindir}/sshfs\" \\\n   \"${prefix}/${sbindir}/mount.fuse.sshfs\"\n"
  }
]