Full Code of libfuse/sshfs for AI

master 9e35c39ba83f cached
33 files
206.2 KB
60.4k tokens
294 symbols
1 requests
Download .txt
Showing preview only (216K chars total). Download the full file or copy to clipboard to get everything.
Repository: libfuse/sshfs
Branch: master
Commit: 9e35c39ba83f
Files: 33
Total size: 206.2 KB

Directory structure:
gitextract_ux8sc0nm/

├── .appveyor.yml
├── .dir-locals.el
├── .git-blame-ignore-revs
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── issue-report.md
│   └── workflows/
│       └── build-ubuntu.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .travis.yml
├── AUTHORS
├── COPYING
├── ChangeLog.rst
├── README.md
├── cache.c
├── cache.h
├── compat/
│   ├── darwin_compat.c
│   └── darwin_compat.h
├── make_release_tarball.sh
├── meson.build
├── sshfs.c
├── sshfs.rst
├── test/
│   ├── .gitignore
│   ├── appveyor-build.sh
│   ├── conftest.py
│   ├── lint.sh
│   ├── lsan_suppress.txt
│   ├── meson.build
│   ├── pytest.ini
│   ├── test_sshfs.py
│   ├── travis-build.sh
│   ├── travis-install.sh
│   ├── util.py
│   └── wrong_command.c
└── utils/
    └── install_helper.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .appveyor.yml
================================================
version: '{build}'

install:

# install WinFsp
- appveyor DownloadFile https://github.com/billziss-gh/winfsp/releases/download/v1.4B2/winfsp-1.4.18211.msi
- for %%f in ("winfsp-*.msi") do start /wait msiexec /i %%f /qn INSTALLLEVEL=1000

# install FUSE for Cygwin (64-bit and 32-bit)
- C:\cygwin64\bin\env.exe -i PATH=/bin bash "%ProgramFiles(x86)%\WinFsp\opt\cygfuse\install.sh"
- C:\cygwin\bin\env.exe -i PATH=/bin bash "%ProgramFiles(x86)%\WinFsp\opt\cygfuse\install.sh"

# install additional Cygwin packages (64-bit and 32-bit)
- 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
- 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

build_script:
- C:\cygwin64\bin\env.exe -i PATH=/bin bash test\appveyor-build.sh
- C:\cygwin\bin\env.exe -i PATH=/bin bash test\appveyor-build.sh


================================================
FILE: .dir-locals.el
================================================
((python-mode . ((indent-tabs-mode . nil)))
 (autoconf-mode . ((indent-tabs-mode . t)))
 (c-mode . ((c-file-style . "stroustrup")
	    (indent-tabs-mode . t)
	    (tab-width . 8)
	    (c-basic-offset . 8)
	    (c-file-offsets .
			    ((block-close . 0)
			     (brace-list-close . 0)
			     (brace-list-entry . 0)
			     (brace-list-intro . +)
			     (case-label . 0)
			     (class-close . 0)
			     (defun-block-intro . +)
			     (defun-close . 0)
			     (defun-open . 0)
			     (else-clause . 0)
			     (inclass . +)
			     (label . 0)
			     (statement . 0)
			     (statement-block-intro . +)
			     (statement-case-intro . +)
			     (statement-cont . +)
			     (substatement . +)
			     (topmost-intro . 0))))))


================================================
FILE: .git-blame-ignore-revs
================================================
d54c7ecbd618afb4df524e0d96dec7fe7cc2935d


================================================
FILE: .github/ISSUE_TEMPLATE/issue-report.md
================================================
---
name: Issue report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

PLEASE READ BEFORE REPORTING AN ISSUE

SSHFS does not have any active, regular contributors or developers. The current maintainer continues to apply pull requests and tries to make regular releases, but unfortunately has no capacity to do any development beyond addressing high-impact issues. When reporting bugs, please understand that unless you are including a pull request or are reporting a critical issue, you will probably not get a response.

To prevent the issue tracker from being flooded with issues that no-one is intending to work on, and to give more visibility to critical issues that users should be aware of and that most urgently need attention, I will also close most bug reports once they've been inactive for a while.

Please note that this isn't meant to imply that you haven't found a bug - you most likely have and I'm grateful that you took the time to report it. Unfortunately, SSHFS is a purely volunteer driven project,
and at the moment there simply aren't any volunteers.


================================================
FILE: .github/workflows/build-ubuntu.yml
================================================
name: build and test
on:
  push:

  pull_request:

  workflow_dispatch: # this is a nice option that will enable a button w/ inputs
    inputs:
      git-ref:
        description: Git Ref (Optional)    
        required: false
jobs:
  build-and-test:
    name: Build and test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - uses: actions/setup-python@v4

      - name: Install build dependencies
        run: |
          sudo apt-get update
          sudo apt-get install valgrind gcc ninja-build meson libglib2.0-dev libfuse3-dev

      - name: Install meson
        run: pip3 install meson pytest

      - name: build
        run: |
          mkdir build; cd build
          meson ..
          ninja

      # cd does not persist across steps
      - name: upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: sshfs
          path: build/sshfs

      - name: make ssh into localhost without prompt possible for tests
        run: |
          ssh-keygen -b 2048 -t rsa  -f ~/.ssh/id_rsa -q -N ""
          cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys

      - name: run tests
        run: |
          cd build
          python3 -m pytest test/


================================================
FILE: .gitignore
================================================
#
# NOTE! Don't add files that are generated in specific
# subdirectories here. Add them in the ".gitignore" file
# in that subdirectory instead.
#
# NOTE! Please use 'git ls-files -i --exclude-standard'
# command after changing this file, to see if there are
# any tracked files which get ignored after the change.
*.o
*.lo
*.la
*.gz
\#*#
*.orig
*~
Makefile.in
Makefile
*.m4
stamp-h*
config.*
sshfs.1
/sshfs
/ltmain.sh
/configure
/install-sh
/mkinstalldirs
/missing
/*.cache
/depcomp
/compile
/libtool
/INSTALL
/.pc
/patches
/m4
.deps/
/build


================================================
FILE: .pre-commit-config.yaml
================================================
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v4.0.1
  hooks:
    - id: trailing-whitespace
    - id: end-of-file-fixer
    - id: check-yaml
    - id: check-added-large-files
- repo: https://github.com/jumanjihouse/pre-commit-hooks
  rev: 2.1.5
  hooks:
    - id: shellcheck


================================================
FILE: .travis.yml
================================================
dist: focal

language: c

cache:
  - pip

addons:
  apt:
    packages:
    - shellcheck
    - valgrind
    - gcc
    - clang
    - python-docutils
    - python3-pip
    - python3-setuptools
    - ninja-build
    - meson
    - python3-pytest
    - libglib2.0-dev

install: test/travis-install.sh

jobs:
  include:
    - name: Lint
      script: ./test/lint.sh
      install: skip
    - name: Build + Test
      script: test/travis-build.sh


================================================
FILE: AUTHORS
================================================
Current Maintainer
------------------

None.


Past Maintainers
----------------

Nikolaus Rath <Nikolaus@rath.org> (until 05/2022)
Miklos Szeredi <miklos@szeredi.hu> (until 12/2015)


Contributors (autogenerated list)
---------------------------------

a1346054 <36859588+a1346054@users.noreply.github.com>
Alan Jenkins <alan.christopher.jenkins@gmail.com>
Alexander Neumann <alexander@bumpern.de>
Anatol Pomozov <anatol.pomozov@gmail.com>
Andrew Stone <a@stne.dev>
Antonio Rojas <arojas@archlinux.org>
Benjamin Fleischer <fleiben@gmail.com>
Berserker <berserker.troll@yandex.com>
Bill Zissimopoulos <billziss@navimatics.com>
bjoe2k4 <bjoe2k4@users.noreply.github.com>
Brandon Carter <b-carter@users.noreply.github.com>
Cam Cope <github@camcope.me>
Chris Wolfe <cwolfe@chromium.org>
Clayton G. Hobbs <clay@lakeserv.net>
Daniel Lublin <daniel@lublin.se>
Dominique Martinet <asmadeus@codewreck.org>
DrDaveD <2129743+DrDaveD@users.noreply.github.com>
Fabrice Fontaine <fontaine.fabrice@gmail.com>
gala <gala132@users.noreply.github.com>
Galen Getsov <4815620+ggetsov@users.noreply.github.com>
George Vlahavas <vlahavas@gmail.com>
G.raud Meyer <graud@gmx.com>
harrim4n <git@harrim4n.com>
Jakub Jelen <jjelen@redhat.com>
jeg139 <54814784+jeg139@users.noreply.github.com>
Josh Triplett <josh@joshtriplett.org>
Julio Merino <jmmv@google.com>
Julio Merino <jmmv@meroh.net>
Junichi Uekawa <dancerj@gmail.com>
Junichi Uekawa <dancer@netfort.gr.jp>
kalvdans <github@kalvdans.no-ip.org>
Kim Brose <kim.brose@rwth-aachen.de>
Matthew Berginski <matthew.berginski@gmail.com>
Michael Forney <mforney@mforney.org>
Mike Kelly <mike@pair.com>
Mike Salvatore <mike.s.salvatore@gmail.com>
Miklos Szeredi <miklos@szeredi.hu>
Miklos Szeredi <mszeredi@suse.cz>
mssalvatore <mike.s.salvatore@gmail.com>
Nikolaus Rath <Nikolaus@rath.org>
Percy Jahn <email@percyjahn.de>
Peter Belm <peterbelm@gmail.com>
Peter Wienemann <peter.wienemann@uni-bonn.de>
Qais Patankar <qaisjp@gmail.com>
Quentin Rameau <quinq@fifth.space>
Reid Wagner <wagnerreid@gmail.com>
Rian Hunter <rian@alum.mit.edu>
Rian Hunter <rianhunter@users.noreply.github.com>
Samuel Murray <samuel.murray@outlook.com>
S. D. Cloudt <s.d.cloudt@student.tue.nl>
Simon Arlott <70171+nomis@users.noreply.github.com>
smheidrich <smheidrich@weltenfunktion.de>
sunwire <50745572+sunwire@users.noreply.github.com>
Tim Harder <radhermit@gmail.com>
Timo Savola <timo.savola@iki.fi>
tpoindessous <thomas@poindessous.com>
Viktor Szakats <vszakats@users.noreply.github.com>
Zoltan Kuscsik <zoltan.kuscsik@linaro.org>


================================================
FILE: COPYING
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

                            NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.


================================================
FILE: ChangeLog.rst
================================================
Release 3.7.5 (2025-11-11)
--------------------------

* New maintainers added
* Main documentation updates to using Markdown
* Updated Windows and macOS support
* Added IPv6 support in -o directport
* Added -o vsock option
* Minor bugfixes

Release 3.7.3 (2022-05-26)
--------------------------

* Minor bugfixes.

* This is the last release from the current maintainer. SSHFS is now no longer maintained
  or developed. Github issue tracking and pull requests have therefore been disabled. The
  mailing list (see below) is still available for use.

  If you would like to take over this project, you are welcome to do so. Please fork it
  and develop the fork for a while. Once there has been 6 months of reasonable activity,
  please contact Nikolaus@rath.org and I'll be happy to give you ownership of this
  repository or replace with a pointer to the fork.


Release 3.7.2 (2021-06-08)
--------------------------

* Added a secondary check so if a mkdir request fails with EPERM an access request will be
  tried - returning EEXIST if the access was successful.
	Fixes: https://github.com/libfuse/sshfs/issues/243


Release 3.7.1 (2020-11-09)
--------------------------

* Minor bugfixes.


Release 3.7.0 (2020-01-03)
--------------------------

* New max_conns option enables the use of multiple connections to improve responsiveness
  during large file transfers. Thanks to Timo Savola for doing most of the implementation
  work, and thanks to CEA.fr for sponsoring remaining bugfixes and cleanups!

* The `buflimit` workaround is now disabled by default. The corresponding bug in OpenSSH
  has been fixed in 2007
  (cf. https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=365541#37), so this shouldn't be
  needed anymore. If you depend on this workaround, please let the SSHFS maintainers know,
  otherwise support for the workaround will be removed completely in a future version.


Release 3.6.0 (2019-11-03)
--------------------------

* Added "-o direct_io" option.
  This option disables the use of page cache in kernel.
  This is useful for example if the file size is not known before reading it.
  For example if you mount /proc dir from a remote host without the direct_io
  option, the read always will return zero bytes instead of actual data.
* Added --verbose option.
* Fixed a number of compiler warnings.
* Improved performance under OS X.


Release 3.5.2 (2019-04-13)
--------------------------

* Fixed "-o idmap=user" to map both UID and GID on all OSs.
* Fixed improper handling of sequential spaces spaces in "ssh_command" option

Release 3.5.1 (2018-12-22)
--------------------------

* Documentation updates
* Build system updates
* Added "BindInterface" as valid "-o" option.

Release 3.5.0 (2018-08-28)
--------------------------

* Fixed error code returned by rename(), allowing proper fallback.
* Port to Cygwin.

Release 3.4.0 (2018-06-29)
--------------------------

* Make utimens(NULL) result in timestamp "now" -- no more touched files
  dated 1970-01-01
* New `createmode` workaround.
* Fix `fstat` workaround regression.

Release 3.3.2 (2018-04-29)
--------------------------

* New `renamexdev` workaround.

Release 3.3.1 (2017-10-25)
--------------------------

* Manpage is now installed in correct directory.
* SSHFS now supports (or rather: ignores) some options that it may
  receive as result of being mounted from ``/etc/mtab``. This includes
  things like ``user``, ``netdev``, or ``auto``.

SSHFS 3.3.0 (2017-09-20)
------------------------

* Dropped support for writeback caching (and, as a consequence,
  "unreliable append" operation). As of kernel 4.14, the FUSE module's
  writeback implementation is not compatible with network filesystems
  and there are no imminent plans to change that.
* Add support for mounting from /etc/fstab
* Dropped support for building with autotools.
* Added missing options to man page.

Release 3.2.0 (2017-08-06)
--------------------------

* Re-enabled writeback cache.
* SSHFS now supports O_APPEND.

Release 3.1.0 (2017-08-04)
--------------------------

* Temporarily disabled the writeback cache feature, since there
  have been reports of dataloss when appending to files when
  writeback caching is enabled.

* Fixed a crash due to a race condition when listing
  directory contents.

* For improved backwards compatibility, SSHFS now also silently
  accepts the old ``-o cache_*`` options.

Release 3.0.0 (2017-07-08)
--------------------------

* sshfs now requires libfuse 3.1.0 or newer.
* When supported by the kernel, sshfs now uses writeback caching.
* The `cache` option has been renamed to `dir_cache` for clarity.
* Added unit tests
* --debug now behaves like -o debug_sshfs, i.e. it enables sshfs
  debugging messages rather than libfuse debugging messages.
* Documented limited hardlink support.
* Added support for building with Meson.
* Added support for more SSH options.
* Dropped support for the *nodelay* workaround - the last OpenSSH
  version for which this was useful was released in 2006.
* Dropped support for the *nodelaysrv* workaround. The same effect
  (enabling NODELAY on the server side *and* enabling X11 forwarding)
  can be achieved by explicitly passing `-o ForwardX11`
* Removed support for `-o workaround=all`. Workarounds should always
  enabled explicitly and only when needed. There is no point in always
  enabling a potentially changing set of workarounds.

Release 2.9 (2017-04-17)
------------------------

* Improved support for Cygwin.
* Various small bugfixes.

Release 2.8 (2016-06-22)
------------------------

* Added support for the "fsync" extension.
* Fixed a build problem with bitbake

Release 2.7 (2016-03-01)
------------------------

* Integrated osxfuse's copy of sshfs, which means that sshfs now works
  on OS X out of the box.
* Added -o cache_max_size=N option to let users tune the maximum size of
  the cache in number of entries.
* Added -o cache_clean_interval=N and -o cache_min_clean_interval=N
  options to let users tune the cleaning behavior of the cache.

Release 2.6 (2015-01-28)
------------------------

* New maintainer (Nikolaus Rath <Nikolaus@rath.org>)

Release 2.5 (2014-01-14)
------------------------

* Some performance improvements for large directories.
* New `disable_hardlink` option.
* Various small bugfixes.

Release 2.4 (2012-03-08)
------------------------

* New `slave` option.
* New `idmap`, `uidmap` and `gidmap` options.
* Various small bugfixes.

Release 2.3 (2011-07-01)
------------------------

* Support hard link creation if server is OpenSSH 5.7 or later
* Small improvements and bug fixes
* Check mount point and options before connecting to ssh server
* New 'delay_connect' option

Release 2.2 (2008-10-20)
------------------------

* Handle numerical IPv6 addresses enclosed in square brackets
* Handle commas in usernames

Release 2.1 (2008-07-11)
------------------------

* Small improvements and bug fixes

Release 2.0 (2008-04-23)
------------------------

* Support password authentication with pam_mount

* Support atomic renames if server is OpenSSH 4.9 or later

* Support getting disk usage if server is OpenSSH 5.1 or later

* Small enhancements and bug fixes

What is new in 1.9
------------------

* Fix a serious bug, that could result in sshfs hanging, crashing, or
  reporting out-of-memory

What is new in 1.8
------------------

* Bug fixes

What is new in 1.7
------------------

* Tolerate servers which print a banner on login

* Small improvements

What is new in 1.6
------------------

* Workaround for missing truncate operation on old sftp servers

* Bug fixes

What is new in 1.5
------------------

* Improvements to read performance.  Now both read and write
  throughput should be very close to 'scp'

* If used with FUSE 2.6.0 or later, then perform better data caching.
  This should show dramatic speed improvements when a file is opened
  more than once

* Bug fixes

What is new in 1.4
------------------

* Updated to version 25 of libfuse API

* This means that the 'cp' of readonly file to sshfs bug is finally
  solved (as long as using libfuse 2.5.0 or later *and* Linux 2.6.15
  or later)

* Sshfs now works on FreeBSD

* Added option to "transform" absolute symbolic links

What is new in 1.3
------------------

* Add workaround for failure to rename to an existing file

* Simple user ID mapping

* Estimate disk usage of files based on size

* Report "infinite" disk space

* Bug fixes

What is new in 1.2
------------------

* Better compatibility with different sftp servers

* Automatic reconnect (optional)

What is new in 1.1
------------------

* Performance improvements:

   - directory content caching

   - symlink caching

   - asynchronous writeback

   - readahead

* Fixed '-p' option

What is new in 1.0
------------------

* Initial release


================================================
FILE: README.md
================================================

# SSHFS


## About

SSHFS allows you to mount a remote filesystem using SFTP. Most SSH
servers support and enable this SFTP access by default, so SSHFS is
very simple to use - there's nothing to do on the server-side.


## Development Status


SSHFS is shipped by all major Linux distributions and has been in
production use across a wide range of systems for many years. However,
at present SSHFS does not have any active, regular contributors, and
there are a number of known issues (see the [bugtracker](https://github.com/libfuse/sshfs/issues)).  
The current maintainer continues to apply pull requests and makes regular
releases, but unfortunately has no capacity to do any development
beyond addressing high-impact issues. When reporting bugs, please
understand that unless you are including a pull request or are
reporting a critical issue, you will probably not get a response.


## How to use


Once sshfs is installed (see next section) running it is very simple:

```
sshfs [user@]hostname:[directory] mountpoint
```

It is recommended to run SSHFS as regular user (not as root).  For
this to work the mountpoint must be owned by the user.  If username is
omitted SSHFS will use the local username. If the directory is
omitted, SSHFS will mount the (remote) home directory.  If you need to
enter a password sshfs will ask for it (actually it just runs ssh
which asks for the password if needed).

Also many ssh options can be specified (see the manual pages for
*sftp(1)* and *ssh_config(5)*), including the remote port number
(`-oport=PORT`)

To unmount the filesystem:

```
fusermount -u mountpoint
```

On BSD and macOS, to unmount the filesystem:

```
umount mountpoint
```

### Bypassing SSH

#### Using directport

Using 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.

On server (listen on port 1234 using socat):

`socat tcp-listen:1234,reuseaddr,fork  exec:/usr/lib/openssh/sftp-server`

On client:

`sshfs -o directport=1234 127.0.0.1:/tmp /tmp/mnt`

Note 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.

IPv6 is also possible:

`socat tcp6-listen:1234,reuseaddr,fork exec:/usr/lib/openssh/sftp-server`

`sshfs -o directport=1234 [::1]:/tmp /tmp/mnt`

#### Using vsock

Similarly 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`.

```
# on the host
socat VSOCK-LISTEN:12345 EXEC:"/usr/lib/openssh/sftp-server",nofork
# on the clientside
sshfs -o vsock=2:12345 unused_host: ./tmp
```

## Installation


First, download the latest SSHFS release from
https://github.com/libfuse/sshfs/releases. You also need [libfuse](http://github.com/libfuse/libfuse) 3.1.0 or newer (or a
similar library that provides a libfuse3 compatible interface for your operating
system). Finally, you need the [Glib](https://developer.gnome.org/glib/stable/) library with development headers (which should be
available from your operating system's package manager).

To build and install, we recommend to use [Meson](http://mesonbuild.com/) (version 0.38 or
newer) and [Ninja](https://ninja-build.org/).  After extracting the sshfs tarball, create a
(temporary) build directory and run Meson:

```
$ mkdir build; cd build
$ meson ..
```

Normally, the default build options will work fine. If you
nevertheless want to adjust them, you can do so with the *mesonconf*
command:

```
$ mesonconf                  # list options
$ mesonconf -D strip=true    # set an option
```

To build, test and install SSHFS, you then use Ninja (running the
tests requires the [py.test](http://www.pytest.org/) Python module):

```
$ ninja
$ python3 -m pytest test/    # optional, but recommended
$ sudo ninja install
```

## Getting Help


If you need help, please ask on the <fuse-sshfs@lists.sourceforge.net>
mailing list (subscribe at
https://lists.sourceforge.net/lists/listinfo/fuse-sshfs).

Please report any bugs on the GitHub issue tracker at
https://github.com/libfuse/sshfs/issues.

## Packaging Status


<a href="https://repology.org/project/fusefs:sshfs/versions">
    <img src="https://repology.org/badge/vertical-allrepos/fusefs:sshfs.svg" alt="Packaging status" >
</a>


================================================
FILE: cache.c
================================================
/*
  Caching file system proxy
  Copyright (C) 2004  Miklos Szeredi <miklos@szeredi.hu>

  This program can be distributed under the terms of the GNU GPL.
  See the file COPYING.
*/

#include "cache.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib.h>
#include <pthread.h>
#include <sys/stat.h>

#define DEFAULT_CACHE_TIMEOUT_SECS 20
#define DEFAULT_MAX_CACHE_SIZE 10000
#define DEFAULT_CACHE_CLEAN_INTERVAL_SECS 60
#define DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS 5

struct cache {
	int on;
	unsigned int stat_timeout_secs;
	unsigned int dir_timeout_secs;
	unsigned int link_timeout_secs;
	unsigned int max_size;
	unsigned int clean_interval_secs;
	unsigned int min_clean_interval_secs;
	struct fuse_operations *next_oper;
	GHashTable *table;
	pthread_mutex_t lock;
	time_t last_cleaned;
	uint64_t write_ctr;
};

static struct cache cache;

struct node {
	struct stat stat;
	time_t stat_valid;
	GPtrArray *dir;
	time_t dir_valid;
	char *link;
	time_t link_valid;
	time_t valid;
};

struct readdir_handle {
	const char *path;
	void *buf;
	fuse_fill_dir_t filler;
	GPtrArray *dir;
	uint64_t wrctr;
};

struct file_handle {
	/* Did we send an open request to the underlying fs? */
	int is_open;

	/* If so, this will hold its handle */
	unsigned long fs_fh;
};

struct cache_dirent {
	char *name;
	struct stat stat;
};

static void free_node(gpointer node_)
{
	struct node *node = (struct node *) node_;
	if (node->dir != NULL) {
		g_ptr_array_free(node->dir, TRUE);
  }
	g_free(node);
}

static void free_cache_dirent(gpointer data) {
	struct cache_dirent *cache_dirent = (struct cache_dirent *) data;
	if (cache_dirent != NULL) {
		g_free(cache_dirent->name);
		g_free(cache_dirent);
	}
}

static int cache_clean_entry(void *key_, struct node *node, time_t *now)
{
	(void) key_;
	if (*now > node->valid)
		return TRUE;
	else
		return FALSE;
}

static void cache_clean(void)
{
	time_t now = time(NULL);
	if (now > cache.last_cleaned + cache.min_clean_interval_secs &&
	    (g_hash_table_size(cache.table) > cache.max_size ||
	     now > cache.last_cleaned + cache.clean_interval_secs)) {
		g_hash_table_foreach_remove(cache.table,
					    (GHRFunc) cache_clean_entry, &now);
		cache.last_cleaned = now;
	}
}

static struct node *cache_lookup(const char *path)
{
	return (struct node *) g_hash_table_lookup(cache.table, path);
}

static void cache_purge(const char *path)
{
	g_hash_table_remove(cache.table, path);
}

static void cache_purge_parent(const char *path)
{
	const char *s = strrchr(path, '/');
	if (s) {
		if (s == path)
			g_hash_table_remove(cache.table, "/");
		else {
			char *parent = g_strndup(path, s - path);
			cache_purge(parent);
			g_free(parent);
		}
	}
}

void cache_invalidate(const char *path)
{
	pthread_mutex_lock(&cache.lock);
	cache_purge(path);
	pthread_mutex_unlock(&cache.lock);
}

static void cache_invalidate_write(const char *path)
{
	pthread_mutex_lock(&cache.lock);
	cache_purge(path);
	cache.write_ctr++;
	pthread_mutex_unlock(&cache.lock);
}

static void cache_invalidate_dir(const char *path)
{
	pthread_mutex_lock(&cache.lock);
	cache_purge(path);
	cache_purge_parent(path);
	pthread_mutex_unlock(&cache.lock);
}

static int cache_del_children(const char *key, void *val_, const char *path)
{
	(void) val_;
	if (strncmp(key, path, strlen(path)) == 0)
		return TRUE;
	else
		return FALSE;
}

static void cache_do_rename(const char *from, const char *to)
{
	pthread_mutex_lock(&cache.lock);
	g_hash_table_foreach_remove(cache.table, (GHRFunc) cache_del_children,
				    (char *) from);
	cache_purge(from);
	cache_purge(to);
	cache_purge_parent(from);
	cache_purge_parent(to);
	pthread_mutex_unlock(&cache.lock);
}

static struct node *cache_get(const char *path)
{
	struct node *node = cache_lookup(path);
	if (node == NULL) {
		char *pathcopy = g_strdup(path);
		node = g_new0(struct node, 1);
		g_hash_table_insert(cache.table, pathcopy, node);
	}
	return node;
}

void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr)
{
	struct node *node;

	pthread_mutex_lock(&cache.lock);
	if (wrctr == cache.write_ctr) {
		node = cache_get(path);
		node->stat = *stbuf;
		node->stat_valid = time(NULL) + cache.stat_timeout_secs;
		if (node->stat_valid > node->valid)
			node->valid = node->stat_valid;
		cache_clean();
	}
	pthread_mutex_unlock(&cache.lock);
}

static void cache_add_dir(const char *path, GPtrArray *dir)
{
	struct node *node;

	pthread_mutex_lock(&cache.lock);
	node = cache_get(path);
	if (node->dir != NULL) {
		g_ptr_array_free(node->dir, TRUE);
  }
	node->dir = dir;
	node->dir_valid = time(NULL) + cache.dir_timeout_secs;
	if (node->dir_valid > node->valid)
		node->valid = node->dir_valid;
	cache_clean();
	pthread_mutex_unlock(&cache.lock);
}

static size_t my_strnlen(const char *s, size_t maxsize)
{
	const char *p;
	for (p = s; maxsize && *p; maxsize--, p++);
	return p - s;
}

static void cache_add_link(const char *path, const char *link, size_t size)
{
	struct node *node;

	pthread_mutex_lock(&cache.lock);
	node = cache_get(path);
	g_free(node->link);
	node->link = g_strndup(link, my_strnlen(link, size-1));
	node->link_valid = time(NULL) + cache.link_timeout_secs;
	if (node->link_valid > node->valid)
		node->valid = node->link_valid;
	cache_clean();
	pthread_mutex_unlock(&cache.lock);
}

static int cache_get_attr(const char *path, struct stat *stbuf)
{
	struct node *node;
	int err = -EAGAIN;
	pthread_mutex_lock(&cache.lock);
	node = cache_lookup(path);
	if (node != NULL) {
		time_t now = time(NULL);
		if (node->stat_valid - now >= 0) {
			*stbuf = node->stat;
			err = 0;
		}
	}
	pthread_mutex_unlock(&cache.lock);
	return err;
}

uint64_t cache_get_write_ctr(void)
{
	uint64_t res;

	pthread_mutex_lock(&cache.lock);
	res = cache.write_ctr;
	pthread_mutex_unlock(&cache.lock);

	return res;
}

static void *cache_init(struct fuse_conn_info *conn,
                        struct fuse_config *cfg)
{
	void *res;
	res = cache.next_oper->init(conn, cfg);

	// Cache requires a path for each request
	cfg->nullpath_ok = 0;

	return res;
}

static int cache_getattr(const char *path, struct stat *stbuf,
			 struct fuse_file_info *fi)
{
	int err = cache_get_attr(path, stbuf);
	if (err) {
		uint64_t wrctr = cache_get_write_ctr();
		err = cache.next_oper->getattr(path, stbuf, fi);
		if (!err)
			cache_add_attr(path, stbuf, wrctr);
	}
	return err;
}

static int cache_readlink(const char *path, char *buf, size_t size)
{
	struct node *node;
	int err;

	pthread_mutex_lock(&cache.lock);
	node = cache_lookup(path);
	if (node != NULL) {
		time_t now = time(NULL);
		if (node->link_valid - now >= 0) {
			strncpy(buf, node->link, size-1);
			buf[size-1] = '\0';
			pthread_mutex_unlock(&cache.lock);
			return 0;
		}
	}
	pthread_mutex_unlock(&cache.lock);
	err = cache.next_oper->readlink(path, buf, size);
	if (!err)
		cache_add_link(path, buf, size);

	return err;
}


static int cache_opendir(const char *path, struct fuse_file_info *fi)
{
	(void) path;
	struct file_handle *cfi;

	cfi = malloc(sizeof(struct file_handle));
	if(cfi == NULL)
		return -ENOMEM;
	cfi->is_open = 0;
	fi->fh = (unsigned long) cfi;
	return 0;
}

static int cache_releasedir(const char *path, struct fuse_file_info *fi)
{
	int err;
	struct file_handle *cfi;

	cfi = (struct file_handle*) fi->fh;

	if(cfi->is_open) {
		fi->fh = cfi->fs_fh;
		err = cache.next_oper->releasedir(path, fi);
	} else
		err = 0;

	free(cfi);
	return err;
}

static int cache_dirfill (void *buf, const char *name,
			  const struct stat *stbuf, off_t off,
			  enum fuse_fill_dir_flags flags)
{
	int err;
	struct readdir_handle *ch;

	ch = (struct readdir_handle*) buf;
	err = ch->filler(ch->buf, name, stbuf, off, flags);
	if (!err) {
		struct cache_dirent *cdent = g_malloc(sizeof(struct cache_dirent));
		cdent->name = g_strdup(name);
		cdent->stat = *stbuf;
		g_ptr_array_add(ch->dir, cdent);
		if (stbuf->st_mode & S_IFMT) {
			char *fullpath;
			const char *basepath = !ch->path[1] ? "" : ch->path;

			fullpath = g_strdup_printf("%s/%s", basepath, name);
			cache_add_attr(fullpath, stbuf, ch->wrctr);
			g_free(fullpath);
		}
	}
	return err;
}

static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
			 off_t offset, struct fuse_file_info *fi,
			 enum fuse_readdir_flags flags)
{
	struct readdir_handle ch;
	struct file_handle *cfi;
	int err;
	GPtrArray *dir;
	struct node *node;
	struct cache_dirent **cdent;

	assert(offset == 0);

	pthread_mutex_lock(&cache.lock);
	node = cache_lookup(path);
	if (node != NULL && node->dir != NULL) {
		time_t now = time(NULL);
		if (node->dir_valid - now >= 0) {
			for(cdent = (struct cache_dirent**)node->dir->pdata; *cdent != NULL; cdent++) {
				filler(buf, (*cdent)->name, &(*cdent)->stat, 0, 0);
      }
			pthread_mutex_unlock(&cache.lock);
			return 0;
		}
	}
	pthread_mutex_unlock(&cache.lock);

	cfi = (struct file_handle*) fi->fh;
	if(cfi->is_open)
		fi->fh = cfi->fs_fh;
	else {
		if(cache.next_oper->opendir) {
			err = cache.next_oper->opendir(path, fi);
			if(err)
				return err;
		}
		cfi->is_open = 1;
		cfi->fs_fh = fi->fh;
	}

	ch.path = path;
	ch.buf = buf;
	ch.filler = filler;
	ch.dir = g_ptr_array_new();
	g_ptr_array_set_free_func(ch.dir, free_cache_dirent);
	ch.wrctr = cache_get_write_ctr();
	err = cache.next_oper->readdir(path, &ch, cache_dirfill, offset, fi, flags);
	g_ptr_array_add(ch.dir, NULL);
	dir = ch.dir;
	if (!err) {
		cache_add_dir(path, dir);
	} else {
		g_ptr_array_free(dir, TRUE);
	}

	return err;
}

static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
{
	int err = cache.next_oper->mknod(path, mode, rdev);
	if (!err)
		cache_invalidate_dir(path);
	return err;
}

static int cache_mkdir(const char *path, mode_t mode)
{
	int err = cache.next_oper->mkdir(path, mode);
	if (!err)
		cache_invalidate_dir(path);
	return err;
}

static int cache_unlink(const char *path)
{
	int err = cache.next_oper->unlink(path);
	if (!err)
		cache_invalidate_dir(path);
	return err;
}

static int cache_rmdir(const char *path)
{
	int err = cache.next_oper->rmdir(path);
	if (!err)
		cache_invalidate_dir(path);
	return err;
}

static int cache_symlink(const char *from, const char *to)
{
	int err = cache.next_oper->symlink(from, to);
	if (!err)
		cache_invalidate_dir(to);
	return err;
}

static int cache_rename(const char *from, const char *to, unsigned int flags)
{
	int err = cache.next_oper->rename(from, to, flags);
	if (!err)
		cache_do_rename(from, to);
	return err;
}

static int cache_link(const char *from, const char *to)
{
	int err = cache.next_oper->link(from, to);
	if (!err) {
		cache_invalidate(from);
		cache_invalidate_dir(to);
	}
	return err;
}

static int cache_chmod(const char *path, mode_t mode,
                       struct fuse_file_info *fi)
{
	int err = cache.next_oper->chmod(path, mode, fi);
	if (!err)
		cache_invalidate(path);
	return err;
}

static int cache_chown(const char *path, uid_t uid, gid_t gid,
                       struct fuse_file_info *fi)
{
	int err = cache.next_oper->chown(path, uid, gid, fi);
	if (!err)
		cache_invalidate(path);
	return err;
}

static int cache_utimens(const char *path, const struct timespec tv[2],
			 struct fuse_file_info *fi)
{
	int err = cache.next_oper->utimens(path, tv, fi);
	if (!err)
		cache_invalidate(path);
	return err;
}

static int cache_write(const char *path, const char *buf, size_t size,
                       off_t offset, struct fuse_file_info *fi)
{
	int res = cache.next_oper->write(path, buf, size, offset, fi);
	if (res >= 0)
		cache_invalidate_write(path);
	return res;
}

static int cache_create(const char *path, mode_t mode,
                        struct fuse_file_info *fi)
{
	int err = cache.next_oper->create(path, mode, fi);
	if (!err)
		cache_invalidate_dir(path);
	return err;
}

static int cache_truncate(const char *path, off_t size,
			  struct fuse_file_info *fi)
{
	int err = cache.next_oper->truncate(path, size, fi);
	if (!err)
		cache_invalidate(path);
	return err;
}

static void cache_fill(struct fuse_operations *oper,
		       struct fuse_operations *cache_oper)
{
	cache_oper->access   = oper->access;
	cache_oper->chmod    = oper->chmod ? cache_chmod : NULL;
	cache_oper->chown    = oper->chown ? cache_chown : NULL;
	cache_oper->create   = oper->create ? cache_create : NULL;
	cache_oper->flush    = oper->flush;
	cache_oper->fsync    = oper->fsync;
	cache_oper->getattr  = oper->getattr ? cache_getattr : NULL;
	cache_oper->getxattr = oper->getxattr;
	cache_oper->init     = cache_init;
	cache_oper->link     = oper->link ? cache_link : NULL;
	cache_oper->listxattr = oper->listxattr;
	cache_oper->mkdir    = oper->mkdir ? cache_mkdir : NULL;
	cache_oper->mknod    = oper->mknod ? cache_mknod : NULL;
	cache_oper->open     = oper->open;
	cache_oper->opendir  = cache_opendir;
	cache_oper->read     = oper->read;
	cache_oper->readdir  = oper->readdir ? cache_readdir : NULL;
	cache_oper->readlink = oper->readlink ? cache_readlink : NULL;
	cache_oper->release  = oper->release;
	cache_oper->releasedir = cache_releasedir;
	cache_oper->removexattr = oper->removexattr;
	cache_oper->rename   = oper->rename ? cache_rename : NULL;
	cache_oper->rmdir    = oper->rmdir ? cache_rmdir : NULL;
	cache_oper->setxattr = oper->setxattr;
	cache_oper->statfs   = oper->statfs;
	cache_oper->symlink  = oper->symlink ? cache_symlink : NULL;
	cache_oper->truncate = oper->truncate ? cache_truncate : NULL;
	cache_oper->unlink   = oper->unlink ? cache_unlink : NULL;
	cache_oper->utimens  = oper->utimens ? cache_utimens : NULL;
	cache_oper->write    = oper->write ? cache_write : NULL;
}

struct fuse_operations *cache_wrap(struct fuse_operations *oper)
{
	static struct fuse_operations cache_oper;
	cache.next_oper = oper;

	cache_fill(oper, &cache_oper);
	pthread_mutex_init(&cache.lock, NULL);
	cache.table = g_hash_table_new_full(g_str_hash, g_str_equal,
					    g_free, free_node);
	if (cache.table == NULL) {
		fprintf(stderr, "failed to create cache\n");
		return NULL;
	}
	return &cache_oper;
}

static const struct fuse_opt cache_opts[] = {
	{ "dcache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
	{ "dcache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
	{ "dcache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
	{ "dcache_stat_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
	{ "dcache_dir_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
	{ "dcache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
	{ "dcache_max_size=%u", offsetof(struct cache, max_size), 0 },
	{ "dcache_clean_interval=%u", offsetof(struct cache,
					       clean_interval_secs), 0 },
	{ "dcache_min_clean_interval=%u", offsetof(struct cache,
						   min_clean_interval_secs), 0 },

	/* For backwards compatibility */
	{ "cache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
	{ "cache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
	{ "cache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
	{ "cache_stat_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
	{ "cache_dir_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
	{ "cache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
	{ "cache_max_size=%u", offsetof(struct cache, max_size), 0 },
	{ "cache_clean_interval=%u", offsetof(struct cache,
					       clean_interval_secs), 0 },
	{ "cache_min_clean_interval=%u", offsetof(struct cache,
						   min_clean_interval_secs), 0 },
	FUSE_OPT_END
};

int cache_parse_options(struct fuse_args *args)
{
	cache.stat_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS;
	cache.dir_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS;
	cache.link_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS;
	cache.max_size = DEFAULT_MAX_CACHE_SIZE;
	cache.clean_interval_secs = DEFAULT_CACHE_CLEAN_INTERVAL_SECS;
	cache.min_clean_interval_secs = DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS;

	return fuse_opt_parse(args, &cache, cache_opts, NULL);
}


================================================
FILE: cache.h
================================================
/*
    Caching file system proxy
    Copyright (C) 2004  Miklos Szeredi <miklos@szeredi.hu>

    This program can be distributed under the terms of the GNU GPL.
    See the file COPYING.
*/

#include <fuse.h>
#include <fuse_opt.h>

struct fuse_operations *cache_wrap(struct fuse_operations *oper);
int cache_parse_options(struct fuse_args *args);
void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr);
void cache_invalidate(const char *path);
uint64_t cache_get_write_ctr(void);


================================================
FILE: compat/darwin_compat.c
================================================
/*
 * Copyright (c) 2006-2008 Amit Singh/Google Inc.
 * Copyright (c) 2012 Anatol Pomozov
 * Copyright (c) 2011-2013 Benjamin Fleischer
 */

#include "darwin_compat.h"

#include <assert.h>
#include <errno.h>
#include <sys/types.h>

/*
 * Semaphore implementation based on:
 *
 * Copyright (C) 2000,02 Free Software Foundation, Inc.
 * This file is part of the GNU C Library.
 * Written by Ga<EB>l Le Mignot <address@hidden>
 *
 * The GNU C Library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * The GNU C Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with the GNU C Library; see the file COPYING.LIB.  If not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/* Semaphores */

#define __SEM_ID_NONE  ((int)0x0)
#define __SEM_ID_LOCAL ((int)0xcafef00d)

/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_init.html */
int
darwin_sem_init(darwin_sem_t *sem, int pshared, unsigned int value)
{
    if (pshared) {
        errno = ENOSYS;
        return -1;
    }

    sem->id = __SEM_ID_NONE;

    if (pthread_cond_init(&sem->__data.local.count_cond, NULL)) {
        goto cond_init_fail;
    }

    if (pthread_mutex_init(&sem->__data.local.count_lock, NULL)) {
        goto mutex_init_fail;
    }

    sem->__data.local.count = value;
    sem->id = __SEM_ID_LOCAL;

    return 0;

mutex_init_fail:

    pthread_cond_destroy(&sem->__data.local.count_cond);

cond_init_fail:

    return -1;
}

/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_destroy.html */
int
darwin_sem_destroy(darwin_sem_t *sem)
{
    int res = 0;

    pthread_mutex_lock(&sem->__data.local.count_lock);

    sem->id = __SEM_ID_NONE;
    pthread_cond_broadcast(&sem->__data.local.count_cond);

    if (pthread_cond_destroy(&sem->__data.local.count_cond)) {
        res = -1;
    }

    pthread_mutex_unlock(&sem->__data.local.count_lock);

    if (pthread_mutex_destroy(&sem->__data.local.count_lock)) {
        res = -1;
    }

    return res;
}

int
darwin_sem_getvalue(darwin_sem_t *sem, unsigned int *sval)
{
    int res = 0;

    pthread_mutex_lock(&sem->__data.local.count_lock);

    if (sem->id != __SEM_ID_LOCAL) {
        res = -1;
        errno = EINVAL;
    } else {
        *sval = sem->__data.local.count;
    }

    pthread_mutex_unlock(&sem->__data.local.count_lock);

    return res;
}

/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_post.html */
int
darwin_sem_post(darwin_sem_t *sem)
{
    int res = 0;

    pthread_mutex_lock(&sem->__data.local.count_lock);

    if (sem->id != __SEM_ID_LOCAL) {
        res = -1;
        errno = EINVAL;
    } else if (sem->__data.local.count < DARWIN_SEM_VALUE_MAX) {
        sem->__data.local.count++;
        if (sem->__data.local.count == 1) {
            pthread_cond_signal(&sem->__data.local.count_cond);
        }
    } else {
        errno = ERANGE;
        res = -1;
    }

    pthread_mutex_unlock(&sem->__data.local.count_lock);

    return res;
}

/* http://www.opengroup.org/onlinepubs/009695399/functions/sem_timedwait.html */
int
darwin_sem_timedwait(darwin_sem_t *sem, const struct timespec *abs_timeout)
{
    int res = 0;

    if (abs_timeout &&
        (abs_timeout->tv_nsec < 0 || abs_timeout->tv_nsec >= 1000000000)) {
        errno = EINVAL;
        return -1;
    }

    pthread_cleanup_push((void(*)(void*))&pthread_mutex_unlock,
                 &sem->__data.local.count_lock);

    pthread_mutex_lock(&sem->__data.local.count_lock);

    if (sem->id != __SEM_ID_LOCAL) {
        errno = EINVAL;
        res = -1;
    } else {
        if (!sem->__data.local.count) {
            res = pthread_cond_timedwait(&sem->__data.local.count_cond,
                             &sem->__data.local.count_lock,
                             abs_timeout);
        }
        if (res) {
            assert(res == ETIMEDOUT);
            res = -1;
            errno = ETIMEDOUT;
        } else if (sem->id != __SEM_ID_LOCAL) {
            res = -1;
            errno = EINVAL;
        } else {
            sem->__data.local.count--;
        }
    }

    pthread_cleanup_pop(1);

    return res;
}

/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_trywait.html */
int
darwin_sem_trywait(darwin_sem_t *sem)
{
    int res = 0;

    pthread_mutex_lock(&sem->__data.local.count_lock);

    if (sem->id != __SEM_ID_LOCAL) {
        res = -1;
        errno = EINVAL;
    } else if (sem->__data.local.count) {
        sem->__data.local.count--;
    } else {
        res = -1;
        errno = EAGAIN;
    }

    pthread_mutex_unlock (&sem->__data.local.count_lock);

    return res;
}

/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_wait.html */
int
darwin_sem_wait(darwin_sem_t *sem)
{
    int res = 0;

    pthread_cleanup_push((void(*)(void*))&pthread_mutex_unlock,
                 &sem->__data.local.count_lock);

    pthread_mutex_lock(&sem->__data.local.count_lock);

    if (sem->id != __SEM_ID_LOCAL) {
        errno = EINVAL;
        res = -1;
    } else {
        if (!sem->__data.local.count) {
            pthread_cond_wait(&sem->__data.local.count_cond,
                      &sem->__data.local.count_lock);
            if (!sem->__data.local.count) {
                /* spurious wakeup, assume it is an interruption */
                res = -1;
                errno = EINTR;
                goto out;
            }
        }
        if (sem->id != __SEM_ID_LOCAL) {
            res = -1;
            errno = EINVAL;
        } else {
            sem->__data.local.count--;
        }
    }

out:
    pthread_cleanup_pop(1);

    return res;
}


================================================
FILE: compat/darwin_compat.h
================================================
/*
 * Copyright (c) 2006-2008 Amit Singh/Google Inc.
 * Copyright (c) 2011-2013 Benjamin Fleischer
 */

#ifndef _DARWIN_COMPAT_
#define _DARWIN_COMPAT_

#include <pthread.h>

/* Semaphores */

typedef struct darwin_sem {
    int id;
    union {
        struct
        {
            unsigned int    count;
            pthread_mutex_t count_lock;
            pthread_cond_t  count_cond;
        } local;
    } __data;
} darwin_sem_t;

#define DARWIN_SEM_VALUE_MAX ((int32_t)32767)

int darwin_sem_init(darwin_sem_t *sem, int pshared, unsigned int value);
int darwin_sem_destroy(darwin_sem_t *sem);
int darwin_sem_getvalue(darwin_sem_t *sem, unsigned int *value);
int darwin_sem_post(darwin_sem_t *sem);
int darwin_sem_timedwait(darwin_sem_t *sem, const struct timespec *abs_timeout);
int darwin_sem_trywait(darwin_sem_t *sem);
int darwin_sem_wait(darwin_sem_t *sem);

/* Caller must not include <semaphore.h> */

typedef darwin_sem_t sem_t;

#define sem_init(s, p, v)   darwin_sem_init(s, p, v)
#define sem_destroy(s)      darwin_sem_destroy(s)
#define sem_getvalue(s, v)  darwin_sem_getvalue(s, v)
#define sem_post(s)         darwin_sem_post(s)
#define sem_timedwait(s, t) darwin_sem_timedwait(s, t)
#define sem_trywait(s)      darwin_sem_trywait(s)
#define sem_wait(s)         darwin_sem_wait(s)

#define SEM_VALUE_MAX       DARWIN_SEM_VALUE_MAX

#endif /* _DARWIN_COMPAT_ */


================================================
FILE: make_release_tarball.sh
================================================
#!/bin/sh
#
# Create tarball from Git tag, removing and adding
# some files.
#

set -e

if [ -z "$1" ]; then
    TAG="$(git tag --list 'sshfs-3*' --sort=-taggerdate | head -1)"
else
    TAG="$1"
fi

echo "Creating release tarball for ${TAG}..."

mkdir "${TAG}"
git archive --format=tar "${TAG}" | tar -x "--directory=${TAG}"
find "${TAG}" -name .gitignore -delete
rm "${TAG}/make_release_tarball.sh" \
   "${TAG}/.travis.yml" \
   "${TAG}/test/travis-build.sh" \
   "${TAG}/test/travis-install.sh"
tar -cJf "${TAG}.tar.xz" "${TAG}/"
gpg --armor --detach-sign "${TAG}.tar.xz"

PREV_TAG="$(git tag --list 'sshfs-3*' --sort=-taggerdate --merged "${TAG}^"| head -1)"
echo "Contributors from ${PREV_TAG} to ${TAG}:"
git log --pretty="format:%an <%aE>" "${PREV_TAG}..${TAG}" | sort -u


================================================
FILE: meson.build
================================================
project('sshfs', 'c', version: '3.7.5',
        meson_version: '>= 0.40',
        default_options: [ 'buildtype=debugoptimized' ])

add_global_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H',
                     '-Wall', '-Wextra', '-Wno-sign-compare',
                     '-Wmissing-declarations', '-Wwrite-strings',
                     language: 'c')

# Some (stupid) GCC versions warn about unused return values even when they are
# casted to void. This makes -Wunused-result pretty useless, since there is no
# way to suppress the warning when we really *want* to ignore the value.
cc = meson.get_compiler('c')
code = '''
__attribute__((warn_unused_result)) int get_4() {
    return 4;
}
int main(void) {
    (void) get_4();
    return 0;
}'''
if not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ])
     message('Compiler warns about unused result even when casting to void')
     add_global_arguments('-Wno-unused-result', language: 'c')
endif


rst2man = find_program('rst2man', 'rst2man.py', required: false)

cfg = configuration_data()

cfg.set_quoted('PACKAGE_VERSION', meson.project_version())

include_dirs = [ include_directories('.') ]
sshfs_sources = ['sshfs.c', 'cache.c']
if target_machine.system() == 'darwin'
  add_global_arguments('-DFUSE_DARWIN_ENABLE_EXTENSIONS=0', language: 'c')
  cfg.set_quoted('IDMAP_DEFAULT', 'user')
  sshfs_sources += [ 'compat/darwin_compat.c' ]
  include_dirs += [ include_directories('compat') ]
else
  cfg.set_quoted('IDMAP_DEFAULT', 'none')
endif

configure_file(output: 'config.h',
               configuration : cfg)

sshfs_deps = [ dependency('fuse3', version: '>= 3.1.0'),
               dependency('glib-2.0'),
               dependency('gthread-2.0') ]

executable('sshfs', sshfs_sources,
           include_directories: include_dirs,
           dependencies: sshfs_deps,
           c_args: ['-DFUSE_USE_VERSION=31'],
           install: true,
           install_dir: get_option('bindir'))

if rst2man.found()
    custom_target('manpages', input: [ 'sshfs.rst' ], output: [ 'sshfs.1' ],
                  command: [rst2man, '@INPUT@', '@OUTPUT@'], install: true,
                  install_dir: join_paths(get_option('mandir'), 'man1'))
else
    message('rst2man not found, not building manual page.')
endif

meson.add_install_script('utils/install_helper.sh',
                         get_option('sbindir'),
                         get_option('bindir'))


subdir('test')


================================================
FILE: sshfs.c
================================================
/*
  SSH file system
  Copyright (C) 2004  Miklos Szeredi <miklos@szeredi.hu>

  This program can be distributed under the terms of the GNU GPL.
  See the file COPYING.
*/

#define _GNU_SOURCE /* avoid implicit declaration of *pt* functions */
#include "config.h"

#include <fuse.h>
#include <fuse_opt.h>
#if !defined(__CYGWIN__)
#  include <fuse_lowlevel.h>
#endif
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#ifndef __APPLE__
#  include <semaphore.h>
#endif
#include <pthread.h>
#include <netdb.h>
#include <signal.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/utsname.h>
#include <sys/mman.h>
#include <poll.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <glib.h>
#include <pwd.h>
#include <grp.h>
#include <limits.h>
#ifdef __APPLE__
#  include <strings.h>
#  include <libgen.h>
#  include <darwin_compat.h>
#endif
#ifdef __linux__
#  include <linux/vm_sockets.h>
#endif

#include "cache.h"

#ifndef MAP_LOCKED
#  define MAP_LOCKED 0
#endif

#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#  define MAP_ANONYMOUS MAP_ANON
#endif


#define SSH_FXP_INIT                1
#define SSH_FXP_VERSION             2
#define SSH_FXP_OPEN                3
#define SSH_FXP_CLOSE               4
#define SSH_FXP_READ                5
#define SSH_FXP_WRITE               6
#define SSH_FXP_LSTAT               7
#define SSH_FXP_FSTAT               8
#define SSH_FXP_SETSTAT             9
#define SSH_FXP_FSETSTAT           10
#define SSH_FXP_OPENDIR            11
#define SSH_FXP_READDIR            12
#define SSH_FXP_REMOVE             13
#define SSH_FXP_MKDIR              14
#define SSH_FXP_RMDIR              15
#define SSH_FXP_REALPATH           16
#define SSH_FXP_STAT               17
#define SSH_FXP_RENAME             18
#define SSH_FXP_READLINK           19
#define SSH_FXP_SYMLINK            20
#define SSH_FXP_STATUS            101
#define SSH_FXP_HANDLE            102
#define SSH_FXP_DATA              103
#define SSH_FXP_NAME              104
#define SSH_FXP_ATTRS             105
#define SSH_FXP_EXTENDED          200
#define SSH_FXP_EXTENDED_REPLY    201

#define SSH_FILEXFER_ATTR_SIZE          0x00000001
#define SSH_FILEXFER_ATTR_UIDGID        0x00000002
#define SSH_FILEXFER_ATTR_PERMISSIONS   0x00000004
#define SSH_FILEXFER_ATTR_ACMODTIME     0x00000008
#define SSH_FILEXFER_ATTR_EXTENDED      0x80000000

#define SSH_FX_OK                            0
#define SSH_FX_EOF                           1
#define SSH_FX_NO_SUCH_FILE                  2
#define SSH_FX_PERMISSION_DENIED             3
#define SSH_FX_FAILURE                       4
#define SSH_FX_BAD_MESSAGE                   5
#define SSH_FX_NO_CONNECTION                 6
#define SSH_FX_CONNECTION_LOST               7
#define SSH_FX_OP_UNSUPPORTED                8

#define SSH_FXF_READ            0x00000001
#define SSH_FXF_WRITE           0x00000002
#define SSH_FXF_APPEND          0x00000004
#define SSH_FXF_CREAT           0x00000008
#define SSH_FXF_TRUNC           0x00000010
#define SSH_FXF_EXCL            0x00000020

/* statvfs@openssh.com f_flag flags */
#define SSH2_FXE_STATVFS_ST_RDONLY	0x00000001
#define SSH2_FXE_STATVFS_ST_NOSUID	0x00000002

#define SFTP_EXT_POSIX_RENAME "posix-rename@openssh.com"
#define SFTP_EXT_STATVFS "statvfs@openssh.com"
#define SFTP_EXT_HARDLINK "hardlink@openssh.com"
#define SFTP_EXT_FSYNC "fsync@openssh.com"

#define PROTO_VERSION 3

#define MY_EOF 1

#define MAX_REPLY_LEN (1 << 17)

#define RENAME_TEMP_CHARS 8

#define SFTP_SERVER_PATH "/usr/lib/sftp-server"

/* Asynchronous readdir parameters */
#define READDIR_START 2
#define READDIR_MAX 32

#define MAX_PASSWORD 1024

/*
   Handling of multiple SFTP connections
   --------------------------------------

   An SFTP server is free to return responses to outstanding requests in arbitrary
   order. However, execution of requests may only be re-ordered and parallelized as long
   as "the results in the responses will be the same as if [the client] had sent the
   requests one at a time and waited for the response in each case".
   (https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.1).

   When using multiple connections, this requirement applies independently for each
   connection. We therefore have to make sure in SSHFS that the way in which we distribute
   requests between connections does not affect the responses that we get.

   In general, this is a tricky problem to solve since for each incoming request we have
   to determine which other in-flight requests may interact with it, and then either
   transmit the request through the same connection or (if there are multiple connections
   involved) wait for the other requests to complete. This means that e.g. a readdir
   request would have to block on most other activity in the same directory, eliminating a
   major advantage of using multiple connections.

   In practice, we can luckily take advantage of the knowledge that most FUSE requests are
   the result of (synchronous) syscalls from userspace that will block until the
   corresponding FUSE response has been sent.

   If -o sshfs_sync is used, SSHFS always waits for the SFTP server response before
   returning a FUSE response to userspace. If userspace makes concurrent system calls,
   there is no ordering guarantee in the first place, so we do not have to worry about
   (re-)ordering within SSHFS either.

   For requests that originate in the kernel (rather than userspace), the situation is
   slightly different. Transmission of FUSE requests and responses is decoupled (there are
   no synchronous calls) and there is no formal specification that defines if reordering
   is permitted. However, the Linux kernel seems to avoid submitting any concurrent
   requests that would give different results depending on execution order and (as of
   kernel 4.20 with writeback caching disabled) the only kind of kernel originated
   requests are read() requests for read-ahead. Since libfuse internally uses multiple
   threads, SSHFS does not necessarily receive requests in the order in which they were
   sent by the kernel. Unless there is a major bug in FUSE, there is therefore no need to
   worry about correct sequencing of such calls even when using multiple SFTP connections.

   If -o sshfs_sync is *not* used, then write() syscalls will return to userspace before
   SSHFS has received responses from the SFTP server. If userspace then issues a second
   syscall related to the same file (and only one connection is in-use), SFTP ordering
   guarantees will ensure that the response takes into account the preceding writes. If
   multiple connections are in use, this has to be ensured by SSHFS instead.

   The easiest way to do so would be to bind specific SFTP connections to file
   handles. Unfortunately, not all requests for the same dentry are guaranteed to come
   from the same file handle and some requests may come without any file handle. We
   therefore maintain a separate mapping from currently open files to SFTP connections. If
   a request comes in for a path contained in sshfs.conntab and its result could be
   changed by a pending write() operation, it will always be executed with the
   associated SFTP connection.

   There are additional subtleties for requests that affect multiple paths.  For example,
   if both source and destination of a rename() request are currently open, which
   connection should be used?

   This problem is again hard in general, but solvable since we only have to worry about
   the effects of pending write() calls. For rename() and link(), it does not matter if a
   pending write is executed before or after the operation. For readdir(), it is possible
   that a pending write() will change the length of the file. However, SSHFS currently
   does not return attribute information for readdir(), so this does not pose problems
   either. Should SSHFS implement a readdirplus() handler (which provides file names and
   attributes) this is a problem that will need to be solved.
*/


#ifdef __APPLE__
   static char sshfs_program_path[PATH_MAX] = { 0 };
#endif /* __APPLE__ */

struct conn {
	pthread_mutex_t lock_write;
	int processing_thread_started;
	int rfd;
	int wfd;
	int connver;
	int req_count;
	int dir_count;
	int file_count;
};

struct buffer {
	uint8_t *p;
	size_t len;
	size_t size;
};

struct dir_handle {
	struct buffer buf;
	struct conn *conn;
};

struct list_head {
	struct list_head *prev;
	struct list_head *next;
};

struct request;
typedef void (*request_func)(struct request *);

struct request {
	unsigned int want_reply;
	sem_t ready;
	uint8_t reply_type;
	uint32_t id;
	int replied;
	int error;
	struct buffer reply;
	struct timeval start;
	void *data;
	request_func end_func;
	size_t len;
	struct list_head list;
	struct conn *conn;
};

struct sshfs_io {
	int num_reqs;
	pthread_cond_t finished;
	int error;
};

struct read_req {
	struct sshfs_io *sio;
	struct list_head list;
	struct buffer data;
	size_t size;
	ssize_t res;
};

struct read_chunk {
	off_t offset;
	size_t size;
	int refs;
	long modifver;
	struct list_head reqs;
	struct sshfs_io sio;
};

struct sshfs_file {
	struct buffer handle;
	struct list_head write_reqs;
	pthread_cond_t write_finished;
	int write_error;
	struct read_chunk *readahead;
	off_t next_pos;
	int is_seq;
	struct conn *conn;
	int connver;
	int modifver;
};

struct conntab_entry {
	unsigned refcount;
	struct conn *conn;
};

struct sshfs {
	char *directport;
	char *ssh_command;
	char *sftp_server;
	struct fuse_args ssh_args;
	char *workarounds;
	int rename_workaround;
	int renamexdev_workaround;
	int truncate_workaround;
	int buflimit_workaround;
	int unrel_append;
	int fstat_workaround;
	int createmode_workaround;
	int transform_symlinks;
	int follow_symlinks;
	int no_check_root;
	int detect_uid;
	int idmap;
	int nomap;
	int disable_hardlink;
	int dir_cache;
	int show_version;
	int show_help;
	int singlethread;
	char *mountpoint;
	char *uid_file;
	char *gid_file;
	GHashTable *uid_map;
	GHashTable *gid_map;
	GHashTable *r_uid_map;
	GHashTable *r_gid_map;
	unsigned max_read;
	unsigned max_write;
	unsigned ssh_ver;
	int sync_write;
	int sync_read;
	int sync_readdir;
	int direct_io;
	int debug;
	int verbose;
	int foreground;
	int reconnect;
	int delay_connect;
	int passive;
	char *host;
	char *base_path;
	GHashTable *reqtab;
	GHashTable *conntab;
	pthread_mutex_t lock;
	unsigned int randseed;
	int max_conns;
	char *vsock;
	struct conn *conns;
	int ptyfd;
	int ptypassivefd;
	int connvers;
	int server_version;
	unsigned remote_uid;
	unsigned local_uid;
	unsigned remote_gid;
	unsigned local_gid;
	int remote_uid_detected;
	unsigned blksize;
	char *progname;
	long modifver;
	unsigned outstanding_len;
	unsigned max_outstanding_len;
	pthread_cond_t outstanding_cond;
	int password_stdin;
	char *password;
	int ext_posix_rename;
	int ext_statvfs;
	int ext_hardlink;
	int ext_fsync;
	struct fuse_operations *op;

	/* statistics */
	uint64_t bytes_sent;
	uint64_t bytes_received;
	uint64_t num_sent;
	uint64_t num_received;
	unsigned int min_rtt;
	unsigned int max_rtt;
	uint64_t total_rtt;
	unsigned int num_connect;
};

static struct sshfs sshfs;

static const char *ssh_opts[] = {
	"AddressFamily",
	"BatchMode",
	"BindAddress",
	"BindInterface",
	"CertificateFile",
	"ChallengeResponseAuthentication",
	"CheckHostIP",
	"Cipher",
	"Ciphers",
	"Compression",
	"CompressionLevel",
	"ConnectionAttempts",
	"ConnectTimeout",
	"ControlMaster",
	"ControlPath",
	"ControlPersist",
	"FingerprintHash",
	"GlobalKnownHostsFile",
	"GSSAPIAuthentication",
	"GSSAPIDelegateCredentials",
	"HostbasedAuthentication",
	"HostbasedKeyTypes",
	"HostKeyAlgorithms",
	"HostKeyAlias",
	"HostName",
	"IdentitiesOnly",
	"IdentityFile",
	"IdentityAgent",
	"IPQoS",
	"KbdInteractiveAuthentication",
	"KbdInteractiveDevices",
	"KexAlgorithms",
	"LocalCommand",
	"LogLevel",
	"MACs",
	"NoHostAuthenticationForLocalhost",
	"NumberOfPasswordPrompts",
	"PasswordAuthentication",
	"PermitLocalCommand",
	"PKCS11Provider",
	"Port",
	"PreferredAuthentications",
	"ProxyCommand",
	"ProxyJump",
	"ProxyUseFdpass",
	"PubkeyAcceptedKeyTypes",
	"PubkeyAuthentication",
	"RekeyLimit",
	"RevokedHostKeys",
	"RhostsRSAAuthentication",
	"RSAAuthentication",
	"ServerAliveCountMax",
	"ServerAliveInterval",
	"SmartcardDevice",
	"StrictHostKeyChecking",
	"TCPKeepAlive",
	"UpdateHostKeys",
	"UsePrivilegedPort",
	"UserKnownHostsFile",
	"VerifyHostKeyDNS",
	"VisualHostKey",
	NULL,
};

enum {
	KEY_PORT,
	KEY_COMPRESS,
	KEY_CONFIGFILE,
};

enum {
	IDMAP_NONE,
	IDMAP_USER,
	IDMAP_FILE,
};

enum {
	NOMAP_IGNORE,
	NOMAP_ERROR,
};

#define SSHFS_OPT(t, p, v) { t, offsetof(struct sshfs, p), v }

static struct fuse_opt sshfs_opts[] = {
	SSHFS_OPT("directport=%s",     directport, 0),
	SSHFS_OPT("ssh_command=%s",    ssh_command, 0),
	SSHFS_OPT("sftp_server=%s",    sftp_server, 0),
	SSHFS_OPT("max_read=%u",       max_read, 0),
	SSHFS_OPT("max_write=%u",      max_write, 0),
	SSHFS_OPT("ssh_protocol=%u",   ssh_ver, 0),
	SSHFS_OPT("-1",                ssh_ver, 1),
	SSHFS_OPT("workaround=%s",     workarounds, 0),
	SSHFS_OPT("idmap=none",        idmap, IDMAP_NONE),
	SSHFS_OPT("idmap=user",        idmap, IDMAP_USER),
	SSHFS_OPT("idmap=file",        idmap, IDMAP_FILE),
	SSHFS_OPT("uidfile=%s",        uid_file, 0),
	SSHFS_OPT("gidfile=%s",        gid_file, 0),
	SSHFS_OPT("nomap=ignore",      nomap, NOMAP_IGNORE),
	SSHFS_OPT("nomap=error",       nomap, NOMAP_ERROR),
	SSHFS_OPT("sshfs_sync",        sync_write, 1),
	SSHFS_OPT("no_readahead",      sync_read, 1),
	SSHFS_OPT("sync_readdir",      sync_readdir, 1),
	SSHFS_OPT("sshfs_debug",       debug, 1),
	SSHFS_OPT("sshfs_verbose",     verbose, 1),
	SSHFS_OPT("reconnect",         reconnect, 1),
	SSHFS_OPT("transform_symlinks", transform_symlinks, 1),
	SSHFS_OPT("follow_symlinks",   follow_symlinks, 1),
	SSHFS_OPT("no_check_root",     no_check_root, 1),
	SSHFS_OPT("password_stdin",    password_stdin, 1),
	SSHFS_OPT("delay_connect",     delay_connect, 1),
	SSHFS_OPT("slave",             passive, 1),
	SSHFS_OPT("passive",           passive, 1),
	SSHFS_OPT("disable_hardlink",  disable_hardlink, 1),
	SSHFS_OPT("dir_cache=yes", dir_cache, 1),
	SSHFS_OPT("dir_cache=no",  dir_cache, 0),
	SSHFS_OPT("direct_io",  direct_io, 1),
	SSHFS_OPT("max_conns=%u",  max_conns, 1),
	SSHFS_OPT("vsock=%s",      vsock, 0),

	SSHFS_OPT("-h",		show_help, 1),
	SSHFS_OPT("--help",	show_help, 1),
	SSHFS_OPT("-V",		show_version, 1),
	SSHFS_OPT("--version",	show_version, 1),
	SSHFS_OPT("-d",		debug, 1),
	SSHFS_OPT("debug",	debug, 1),
	SSHFS_OPT("-v",		verbose, 1),
	SSHFS_OPT("verbose",	verbose, 1),
	SSHFS_OPT("-f",		foreground, 1),
	SSHFS_OPT("-s",		singlethread, 1),

	FUSE_OPT_KEY("-p ",            KEY_PORT),
	FUSE_OPT_KEY("-C",             KEY_COMPRESS),
	FUSE_OPT_KEY("-F ",            KEY_CONFIGFILE),

	/* For backwards compatibility */
	SSHFS_OPT("cache=yes", dir_cache, 1),
	SSHFS_OPT("cache=no",  dir_cache, 0),

	FUSE_OPT_KEY("writeback_cache=no", FUSE_OPT_KEY_DISCARD),
	FUSE_OPT_KEY("unreliable_append", FUSE_OPT_KEY_DISCARD),

	/* These may come in from /etc/fstab - we just ignore them */
	FUSE_OPT_KEY("auto", FUSE_OPT_KEY_DISCARD),
	FUSE_OPT_KEY("noauto", FUSE_OPT_KEY_DISCARD),
	FUSE_OPT_KEY("user", FUSE_OPT_KEY_DISCARD),
	FUSE_OPT_KEY("nouser", FUSE_OPT_KEY_DISCARD),
	FUSE_OPT_KEY("users", FUSE_OPT_KEY_DISCARD),
	FUSE_OPT_KEY("_netdev", FUSE_OPT_KEY_DISCARD),

	FUSE_OPT_END
};

static struct fuse_opt workaround_opts[] = {
	SSHFS_OPT("none",       rename_workaround, 0),
	SSHFS_OPT("none",       truncate_workaround, 0),
	SSHFS_OPT("none",       buflimit_workaround, 0),
	SSHFS_OPT("none",       fstat_workaround, 0),
	SSHFS_OPT("rename",     rename_workaround, 1),
	SSHFS_OPT("norename",   rename_workaround, 0),
	SSHFS_OPT("renamexdev",   renamexdev_workaround, 1),
	SSHFS_OPT("norenamexdev", renamexdev_workaround, 0),
	SSHFS_OPT("truncate",   truncate_workaround, 1),
	SSHFS_OPT("notruncate", truncate_workaround, 0),
	SSHFS_OPT("buflimit",   buflimit_workaround, 1),
	SSHFS_OPT("nobuflimit", buflimit_workaround, 0),
	SSHFS_OPT("fstat",      fstat_workaround, 1),
	SSHFS_OPT("nofstat",    fstat_workaround, 0),
	SSHFS_OPT("createmode",   createmode_workaround, 1),
	SSHFS_OPT("nocreatemode", createmode_workaround, 0),
	FUSE_OPT_END
};

#define DEBUG(format, args...)						\
	do { if (sshfs.debug) fprintf(stderr, format, args); } while(0)

static const char *type_name(uint8_t type)
{
	switch(type) {
	case SSH_FXP_INIT:           return "INIT";
	case SSH_FXP_VERSION:        return "VERSION";
	case SSH_FXP_OPEN:           return "OPEN";
	case SSH_FXP_CLOSE:          return "CLOSE";
	case SSH_FXP_READ:           return "READ";
	case SSH_FXP_WRITE:          return "WRITE";
	case SSH_FXP_LSTAT:          return "LSTAT";
	case SSH_FXP_FSTAT:          return "FSTAT";
	case SSH_FXP_SETSTAT:        return "SETSTAT";
	case SSH_FXP_FSETSTAT:       return "FSETSTAT";
	case SSH_FXP_OPENDIR:        return "OPENDIR";
	case SSH_FXP_READDIR:        return "READDIR";
	case SSH_FXP_REMOVE:         return "REMOVE";
	case SSH_FXP_MKDIR:          return "MKDIR";
	case SSH_FXP_RMDIR:          return "RMDIR";
	case SSH_FXP_REALPATH:       return "REALPATH";
	case SSH_FXP_STAT:           return "STAT";
	case SSH_FXP_RENAME:         return "RENAME";
	case SSH_FXP_READLINK:       return "READLINK";
	case SSH_FXP_SYMLINK:        return "SYMLINK";
	case SSH_FXP_STATUS:         return "STATUS";
	case SSH_FXP_HANDLE:         return "HANDLE";
	case SSH_FXP_DATA:           return "DATA";
	case SSH_FXP_NAME:           return "NAME";
	case SSH_FXP_ATTRS:          return "ATTRS";
	case SSH_FXP_EXTENDED:       return "EXTENDED";
	case SSH_FXP_EXTENDED_REPLY: return "EXTENDED_REPLY";
	default:                     return "???";
	}
}

#define container_of(ptr, type, member) ({				\
			const typeof( ((type *)0)->member ) *__mptr = (ptr); \
			(type *)( (char *)__mptr - offsetof(type,member) );})

#define list_entry(ptr, type, member)		\
	container_of(ptr, type, member)

static void list_init(struct list_head *head)
{
	head->next = head;
	head->prev = head;
}

static void list_add(struct list_head *new, struct list_head *head)
{
	struct list_head *prev = head;
	struct list_head *next = head->next;
	next->prev = new;
	new->next = next;
	new->prev = prev;
	prev->next = new;
}

static void list_del(struct list_head *entry)
{
	struct list_head *prev = entry->prev;
	struct list_head *next = entry->next;
	next->prev = prev;
	prev->next = next;

}

static int list_empty(const struct list_head *head)
{
	return head->next == head;
}

/* given a pointer to the uid/gid, and the mapping table, remap the
 * uid/gid, if necessary */
static inline int translate_id(uint32_t *id, GHashTable *map)
{
	gpointer id_p;
	if (g_hash_table_lookup_extended(map, GUINT_TO_POINTER(*id), NULL, &id_p)) {
		*id = GPOINTER_TO_UINT(id_p);
		return 0;
	}
	switch (sshfs.nomap) {
	case NOMAP_ERROR: return -1;
	case NOMAP_IGNORE: return 0;
	default:
		fprintf(stderr, "internal error\n");
		abort();
	}
}

static inline void buf_init(struct buffer *buf, size_t size)
{
	if (size) {
		buf->p = (uint8_t *) malloc(size);
		if (!buf->p) {
			fprintf(stderr, "sshfs: memory allocation failed\n");
			abort();
		}
	} else
		buf->p = NULL;
	buf->len = 0;
	buf->size = size;
}

static inline void buf_free(struct buffer *buf)
{
	free(buf->p);
}

static inline void buf_finish(struct buffer *buf)
{
	buf->len = buf->size;
}

static inline void buf_clear(struct buffer *buf)
{
	buf_free(buf);
	buf_init(buf, 0);
}

static void buf_resize(struct buffer *buf, size_t len)
{
	buf->size = (buf->len + len + 63) & ~31;
	buf->p = (uint8_t *) realloc(buf->p, buf->size);
	if (!buf->p) {
		fprintf(stderr, "sshfs: memory allocation failed\n");
		abort();
	}
}

static inline void buf_check_add(struct buffer *buf, size_t len)
{
	if (buf->len + len > buf->size)
		buf_resize(buf, len);
}

#define _buf_add_mem(b, d, l)			\
	buf_check_add(b, l);			\
	memcpy(b->p + b->len, d, l);		\
	b->len += l;


static inline void buf_add_mem(struct buffer *buf, const void *data,
                               size_t len)
{
	_buf_add_mem(buf, data, len);
}

static inline void buf_add_buf(struct buffer *buf, const struct buffer *bufa)
{
	_buf_add_mem(buf, bufa->p, bufa->len);
}

static inline void buf_add_uint8(struct buffer *buf, uint8_t val)
{
	_buf_add_mem(buf, &val, 1);
}

static inline void buf_add_uint32(struct buffer *buf, uint32_t val)
{
	uint32_t nval = htonl(val);
	_buf_add_mem(buf, &nval, 4);
}

static inline void buf_add_uint64(struct buffer *buf, uint64_t val)
{
	buf_add_uint32(buf, val >> 32);
	buf_add_uint32(buf, val & 0xffffffff);
}

static inline void buf_add_data(struct buffer *buf, const struct buffer *data)
{
	buf_add_uint32(buf, data->len);
	buf_add_mem(buf, data->p, data->len);
}

static inline void buf_add_string(struct buffer *buf, const char *str)
{
	struct buffer data;
	data.p = (uint8_t *) str;
	data.len = strlen(str);
	buf_add_data(buf, &data);
}

static inline void buf_add_path(struct buffer *buf, const char *path)
{
	char *realpath;

	if (sshfs.base_path[0]) {
		if (path[1]) {
			if (sshfs.base_path[strlen(sshfs.base_path)-1] != '/') {
				realpath = g_strdup_printf("%s/%s",
							   sshfs.base_path,
							   path + 1);
			} else {
				realpath = g_strdup_printf("%s%s",
							   sshfs.base_path,
							   path + 1);
			}
		} else {
			realpath = g_strdup(sshfs.base_path);
		}
	} else {
		if (path[1])
			realpath = g_strdup(path + 1);
		else
			realpath = g_strdup(".");
	}
	buf_add_string(buf, realpath);
	g_free(realpath);
}

static int buf_check_get(struct buffer *buf, size_t len)
{
	if (buf->len + len > buf->size) {
		fprintf(stderr, "buffer too short\n");
		return -1;
	} else
		return 0;
}

static inline int buf_get_mem(struct buffer *buf, void *data, size_t len)
{
	if (buf_check_get(buf, len) == -1)
		return -1;
	memcpy(data, buf->p + buf->len, len);
	buf->len += len;
	return 0;
}

static inline int buf_get_uint8(struct buffer *buf, uint8_t *val)
{
	return buf_get_mem(buf, val, 1);
}

static inline int buf_get_uint32(struct buffer *buf, uint32_t *val)
{
	uint32_t nval;
	if (buf_get_mem(buf, &nval, 4) == -1)
		return -1;
	*val = ntohl(nval);
	return 0;
}

static inline int buf_get_uint64(struct buffer *buf, uint64_t *val)
{
	uint32_t val1;
	uint32_t val2;
	if (buf_get_uint32(buf, &val1) == -1 ||
	    buf_get_uint32(buf, &val2) == -1) {
		return -1;
	}
	*val = ((uint64_t) val1 << 32) + val2;
	return 0;
}

static inline int buf_get_data(struct buffer *buf, struct buffer *data)
{
	uint32_t len;
	if (buf_get_uint32(buf, &len) == -1 || len > buf->size - buf->len)
		return -1;
	buf_init(data, len + 1);
	data->size = len;
	if (buf_get_mem(buf, data->p, data->size) == -1) {
		buf_free(data);
		return -1;
	}
	return 0;
}

static inline int buf_get_string(struct buffer *buf, char **str)
{
	struct buffer data;
	if (buf_get_data(buf, &data) == -1)
		return -1;
	data.p[data.size] = '\0';
	*str = (char *) data.p;
	return 0;
}

static int buf_get_attrs(struct buffer *buf, struct stat *stbuf, int *flagsp)
{
	uint32_t flags;
	uint64_t size = 0;
	uint32_t uid = 0;
	uint32_t gid = 0;
	uint32_t atime = 0;
	uint32_t mtime = 0;
	uint32_t mode = S_IFREG | 0777;

	if (buf_get_uint32(buf, &flags) == -1)
		return -EIO;
	if (flagsp)
		*flagsp = flags;
	if ((flags & SSH_FILEXFER_ATTR_SIZE) &&
	    buf_get_uint64(buf, &size) == -1)
		return -EIO;
	if ((flags & SSH_FILEXFER_ATTR_UIDGID) &&
	    (buf_get_uint32(buf, &uid) == -1 ||
	     buf_get_uint32(buf, &gid) == -1))
		return -EIO;
	if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
	    buf_get_uint32(buf, &mode) == -1)
		return -EIO;
	if ((flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
		if (buf_get_uint32(buf, &atime) == -1 ||
		    buf_get_uint32(buf, &mtime) == -1)
			return -EIO;
	}
	if ((flags & SSH_FILEXFER_ATTR_EXTENDED)) {
		uint32_t extcount;
		unsigned i;
		if (buf_get_uint32(buf, &extcount) == -1)
			return -EIO;
		for (i = 0; i < extcount; i++) {
			struct buffer tmp;
			if (buf_get_data(buf, &tmp) == -1)
				return -EIO;
			buf_free(&tmp);
			if (buf_get_data(buf, &tmp) == -1)
				return -EIO;
			buf_free(&tmp);
		}
	}

	if (sshfs.remote_uid_detected) {
		if (uid == sshfs.remote_uid)
			uid = sshfs.local_uid;
		if (gid == sshfs.remote_gid)
			gid = sshfs.local_gid;
	}
	if (sshfs.idmap == IDMAP_FILE && sshfs.uid_map)
		if (translate_id(&uid, sshfs.uid_map) == -1)
			return -EPERM;
	if (sshfs.idmap == IDMAP_FILE && sshfs.gid_map)
		if (translate_id(&gid, sshfs.gid_map) == -1)
			return -EPERM;

	memset(stbuf, 0, sizeof(struct stat));
	stbuf->st_mode = mode;
	stbuf->st_nlink = 1;
	stbuf->st_size = size;
	if (sshfs.blksize) {
		stbuf->st_blksize = sshfs.blksize;
		stbuf->st_blocks = ((size + sshfs.blksize - 1) &
			~((unsigned long long) sshfs.blksize - 1)) >> 9;
	}
	stbuf->st_uid = uid;
	stbuf->st_gid = gid;
	stbuf->st_atime = atime;
	stbuf->st_ctime = stbuf->st_mtime = mtime;
	return 0;
}

static int buf_get_statvfs(struct buffer *buf, struct statvfs *stbuf)
{
	uint64_t bsize;
	uint64_t frsize;
	uint64_t blocks;
	uint64_t bfree;
	uint64_t bavail;
	uint64_t files;
	uint64_t ffree;
	uint64_t favail;
	uint64_t fsid;
	uint64_t flag;
	uint64_t namemax;

	if (buf_get_uint64(buf, &bsize) == -1 ||
	    buf_get_uint64(buf, &frsize) == -1 ||
	    buf_get_uint64(buf, &blocks) == -1 ||
	    buf_get_uint64(buf, &bfree) == -1 ||
	    buf_get_uint64(buf, &bavail) == -1 ||
	    buf_get_uint64(buf, &files) == -1 ||
	    buf_get_uint64(buf, &ffree) == -1 ||
	    buf_get_uint64(buf, &favail) == -1 ||
	    buf_get_uint64(buf, &fsid) == -1 ||
	    buf_get_uint64(buf, &flag) == -1 ||
	    buf_get_uint64(buf, &namemax) == -1) {
		return -1;
	}

	memset(stbuf, 0, sizeof(struct statvfs));
	stbuf->f_bsize = bsize;
	stbuf->f_frsize = frsize;
	stbuf->f_blocks = blocks;
	stbuf->f_bfree = bfree;
	stbuf->f_bavail = bavail;
	stbuf->f_files = files;
	stbuf->f_ffree = ffree;
	stbuf->f_favail = favail;
	stbuf->f_namemax = namemax;

	return 0;
}

static int buf_get_entries(struct buffer *buf, void *dbuf,
                           fuse_fill_dir_t filler)
{
	uint32_t count;
	unsigned i;

	if (buf_get_uint32(buf, &count) == -1)
		return -EIO;

	for (i = 0; i < count; i++) {
		int err = -1;
		char *name;
		char *longname;
		struct stat stbuf;
		if (buf_get_string(buf, &name) == -1)
			return -EIO;
		if (buf_get_string(buf, &longname) != -1) {
			free(longname);
			err = buf_get_attrs(buf, &stbuf, NULL);
			if (!err) {
				if (sshfs.follow_symlinks &&
				    S_ISLNK(stbuf.st_mode)) {
					stbuf.st_mode = 0;
				}
				filler(dbuf, name, &stbuf, 0, 0);
			}
		}
		free(name);
		if (err)
			return err;
	}
	return 0;
}

static void ssh_add_arg(const char *arg)
{
	if (fuse_opt_add_arg(&sshfs.ssh_args, arg) == -1)
		_exit(1);
}


static int pty_expect_loop(struct conn *conn)
{
	int res;
	char buf[256];
	const char *passwd_str = "assword:";
	int timeout = 60 * 1000; /* 1min timeout for the prompt to appear */
	int passwd_len = strlen(passwd_str);
	int len = 0;
	char c;

	while (1) {
		struct pollfd fds[2];

		fds[0].fd = conn->rfd;
		fds[0].events = POLLIN;
		fds[1].fd = sshfs.ptyfd;
		fds[1].events = POLLIN;
		res = poll(fds, 2, timeout);
		if (res == -1) {
			perror("poll");
			return -1;
		}
		if (res == 0) {
			fprintf(stderr, "Timeout waiting for prompt\n");
			return -1;
		}
		if (fds[0].revents) {
			/*
			 * Something happened on stdout of ssh, this
			 * either means, that we are connected, or
			 * that we are disconnected.  In any case the
			 * password doesn't matter any more.
			 */
			break;
		}

		res = read(sshfs.ptyfd, &c, 1);
		if (res == -1) {
			perror("read");
			return -1;
		}
		if (res == 0) {
			fprintf(stderr, "EOF while waiting for prompt\n");
			return -1;
		}
		buf[len] = c;
		len++;
		if (len == passwd_len) {
			if (memcmp(buf, passwd_str, passwd_len) == 0) {
				write(sshfs.ptyfd, sshfs.password,
				      strlen(sshfs.password));
			}
			memmove(buf, buf + 1, passwd_len - 1);
			len--;
		}
	}

	if (!sshfs.reconnect) {
		size_t size = getpagesize();

		memset(sshfs.password, 0, size);
		munmap(sshfs.password, size);
		sshfs.password = NULL;
	}

	return 0;
}

static struct conn* get_conn(const struct sshfs_file *sf,
			     const char *path)
{
	struct conntab_entry *ce;
	int i;

	if (sshfs.max_conns == 1)
		return &sshfs.conns[0];

	if (sf != NULL)
		return sf->conn;

	if (path != NULL) {
		pthread_mutex_lock(&sshfs.lock);
		ce = g_hash_table_lookup(sshfs.conntab, path);

		if (ce != NULL) {
			struct conn *conn = ce->conn;
			pthread_mutex_unlock(&sshfs.lock);
			return conn;
		}
		pthread_mutex_unlock(&sshfs.lock);
	}

	int best_index = 0;
	uint64_t best_score = ~0ULL; /* smaller is better */
	for (i = 0; i < sshfs.max_conns; i++) {
		uint64_t score = ((uint64_t) sshfs.conns[i].req_count << 43) +
				 ((uint64_t) sshfs.conns[i].dir_count << 22) +
				 ((uint64_t) sshfs.conns[i].file_count << 1) +
				 (uint64_t) (sshfs.conns[i].rfd >= 0 ? 0 : 1);
		if (score < best_score) {
			best_index = i;
			best_score = score;
		}
	}
	return &sshfs.conns[best_index];
}

static int pty_master(char **name)
{
	int mfd;

	mfd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
	if (mfd == -1) {
		perror("failed to open pty");
		return -1;
	}
	if (grantpt(mfd) != 0) {
		perror("grantpt");
		return -1;
	}
	if (unlockpt(mfd) != 0) {
		perror("unlockpt");
		return -1;
	}
	*name = ptsname(mfd);

	return mfd;
}

static void replace_arg(char **argp, const char *newarg)
{
	free(*argp);
	*argp = strdup(newarg);
	if (*argp == NULL) {
		fprintf(stderr, "sshfs: memory allocation failed\n");
		abort();
	}
}

static int start_ssh(struct conn *conn)
{
	char *ptyname = NULL;
	int sockpair[2];
	int pid;

	if (sshfs.password_stdin) {

		sshfs.ptyfd = pty_master(&ptyname);
		if (sshfs.ptyfd == -1)
			return -1;

		sshfs.ptypassivefd = open(ptyname, O_RDWR | O_NOCTTY);
		if (sshfs.ptypassivefd == -1)
			return -1;
	}

	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) == -1) {
		perror("failed to create socket pair");
		return -1;
	}
	conn->rfd = sockpair[0];
	conn->wfd = sockpair[0];

	pid = fork();
	if (pid == -1) {
		perror("failed to fork");
		close(sockpair[1]);
		return -1;
	} else if (pid == 0) {
		int devnull;

		devnull = open("/dev/null", O_WRONLY);

		if (dup2(sockpair[1], 0) == -1 || dup2(sockpair[1], 1) == -1) {
			perror("failed to redirect input/output");
			_exit(1);
		}
		if (!sshfs.verbose && !sshfs.foreground && devnull != -1)
			dup2(devnull, 2);

		close(devnull);
		close(sockpair[0]);
		close(sockpair[1]);

		switch (fork()) {
		case -1:
			perror("failed to fork");
			_exit(1);
		case 0:
			break;
		default:
			_exit(0);
		}
		chdir("/");
		/*
		 * Avoid processes hanging trying to stat() OLDPWD if it is in
		 * the mount point. This can be removed if sshfs opens the
		 * mount point after establishing the ssh connection.
		 */
		unsetenv("OLDPWD");

		if (sshfs.password_stdin) {
			int sfd;

			setsid();
			sfd = open(ptyname, O_RDWR);
			if (sfd == -1) {
				perror(ptyname);
				_exit(1);
			}
			close(sfd);
			close(sshfs.ptypassivefd);
			close(sshfs.ptyfd);
		}

		if (sshfs.debug) {
			int i;

			fprintf(stderr, "executing");
			for (i = 0; i < sshfs.ssh_args.argc; i++)
				fprintf(stderr, " <%s>",
					sshfs.ssh_args.argv[i]);
			fprintf(stderr, "\n");
		}

#if defined(__CYGWIN__)
		/* 
		 * Windows native OpenSSH stdio behavior. For details check
		 * https://github.com/PowerShell/openssh-portable/pull/759
		 */
		putenv("OPENSSH_STDIO_MODE=nonsock");
#endif
		execvp(sshfs.ssh_args.argv[0], sshfs.ssh_args.argv);
		fprintf(stderr, "failed to execute '%s': %s\n",
			sshfs.ssh_args.argv[0], strerror(errno));
		_exit(1);
	}
	waitpid(pid, NULL, 0);
	close(sockpair[1]);
	return 0;
}

static int connect_passive(struct conn *conn)
{
	conn->rfd = STDIN_FILENO;
	conn->wfd = STDOUT_FILENO;
	return 0;
}

static int connect_to(struct conn *conn, char *host, char *port)
{
	int err;
	int sock;
	int opt;
	struct addrinfo *ai;
	struct addrinfo hint;

	memset(&hint, 0, sizeof(hint));
	hint.ai_family = PF_INET;
	if (strstr(host, ":") != NULL) { // only ipv6 should have : in it, normal IP and domains do not.
		hint.ai_family = PF_INET6;
		DEBUG("using ipv6 to connect to host %s\n", host);
	}
	hint.ai_socktype = SOCK_STREAM;
	err = getaddrinfo(host, port, &hint, &ai);
	if (err) {
		fprintf(stderr, "failed to resolve %s:%s: %s\n", host, port,
			gai_strerror(err));
		return -1;
	}
	sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
	if (sock == -1) {
		perror("failed to create socket");
		freeaddrinfo(ai);
		return -1;
	}
	err = connect(sock, ai->ai_addr, ai->ai_addrlen);
	if (err == -1) {
		perror("failed to connect");
		freeaddrinfo(ai);
		close(sock);
		return -1;
	}
	opt = 1;
	err = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
	if (err == -1)
		perror("warning: failed to set TCP_NODELAY");

	freeaddrinfo(ai);

	conn->rfd = sock;
	conn->wfd = sock;
	return 0;
}

static int connect_vsock(struct conn *conn, char *vsock)
{
#ifndef __linux__
	fprintf(stderr, "vsock is not available\n");
	return -1;
#else
	int err;
	int sock;
	struct sockaddr_vm addr;
	unsigned int cid;
	unsigned int port;
	char *delim;

	delim = strchr(vsock, ':');
	if (delim == NULL) {
		fprintf(stderr, "invalid vsock, expecting CID:PORT\n");
		return -1;
	}
	*delim = '\0';
	errno = 0;
	cid = strtoul(vsock, NULL, 10);
	if (errno) {
		perror("invalid cid");
		return -1;
	}
	errno = 0;
	port = strtoul(delim + 1, NULL, 10);
	if (errno) {
		perror("invalid port");
		return -1;
	}

	sock = socket(AF_VSOCK, SOCK_STREAM, 0);
	if (sock == -1) {
		perror("failed to create socket");
		return -1;
	}
	memset(&addr, 0, sizeof(addr));
	addr.svm_family = AF_VSOCK;
	addr.svm_cid = cid;
	addr.svm_port = port;
	err = connect(sock, (const struct sockaddr *)&addr, sizeof(addr));
	if (err == -1) {
		perror("failed to connect vsock");
		close(sock);
		return -1;
	}

	conn->rfd = sock;
	conn->wfd = sock;
	return 0;
#endif
}

static int do_write(struct conn *conn, struct iovec *iov, size_t count)
{
	int res;
	while (count) {
		res = writev(conn->wfd, iov, count);
		if (res == -1) {
			perror("write");
			return -1;
		} else if (res == 0) {
			fprintf(stderr, "zero write\n");
			return -1;
		}
		do {
			if ((unsigned) res < iov->iov_len) {
				iov->iov_len -= res;
				iov->iov_base += res;
				break;
			} else {
				res -= iov->iov_len;
				count --;
				iov ++;
			}
		} while(count);
	}
	return 0;
}

static uint32_t sftp_get_id(void)
{
	static uint32_t idctr;
	return idctr++;
}

static void buf_to_iov(const struct buffer *buf, struct iovec *iov)
{
	iov->iov_base = buf->p;
	iov->iov_len = buf->len;
}

static size_t iov_length(const struct iovec *iov, unsigned long nr_segs)
{
	unsigned long seg;
	size_t ret = 0;

	for (seg = 0; seg < nr_segs; seg++)
		ret += iov[seg].iov_len;
	return ret;
}

#define SFTP_MAX_IOV 3

static int sftp_send_iov(struct conn *conn, uint8_t type, uint32_t id,
                         struct iovec iov[], size_t count)
{
	int res;
	struct buffer buf;
	struct iovec iovout[SFTP_MAX_IOV];
	unsigned i;
	unsigned nout = 0;

	assert(count <= SFTP_MAX_IOV - 1);
	buf_init(&buf, 9);
	buf_add_uint32(&buf, iov_length(iov, count) + 5);
	buf_add_uint8(&buf, type);
	buf_add_uint32(&buf, id);
	buf_to_iov(&buf, &iovout[nout++]);
	for (i = 0; i < count; i++)
		iovout[nout++] = iov[i];
	pthread_mutex_lock(&conn->lock_write);
	res = do_write(conn, iovout, nout);
	pthread_mutex_unlock(&conn->lock_write);
	buf_free(&buf);
	return res;
}

static int do_read(struct conn *conn, struct buffer *buf)
{
	int res;
	uint8_t *p = buf->p;
	size_t size = buf->size;
	while (size) {
		res = read(conn->rfd, p, size);
		if (res == -1) {
			perror("read");
			return -1;
		} else if (res == 0) {
			fprintf(stderr, "remote host has disconnected\n");
			return -1;
		}
		size -= res;
		p += res;
	}
	return 0;
}

static int sftp_read(struct conn *conn, uint8_t *type, struct buffer *buf)
{
	int res;
	struct buffer buf2;
	uint32_t len;
	buf_init(&buf2, 5);
	res = do_read(conn, &buf2);
	if (res != -1) {
		if (buf_get_uint32(&buf2, &len) == -1)
			return -1;
		if (len > MAX_REPLY_LEN) {
			fprintf(stderr, "reply len too large: %u\n", len);
			return -1;
		}
		if (buf_get_uint8(&buf2, type) == -1)
			return -1;
		buf_init(buf, len - 1);
		res = do_read(conn, buf);
	}
	buf_free(&buf2);
	return res;
}

static void request_free(struct request *req)
{
	if (req->end_func)
		req->end_func(req);

	req->conn->req_count--;

	buf_free(&req->reply);
	sem_destroy(&req->ready);
	g_free(req);
}

static void chunk_free(struct read_chunk *chunk)
{
	while (!list_empty(&chunk->reqs)) {
		struct read_req *rreq;

		rreq = list_entry(chunk->reqs.prev, struct read_req, list);
		list_del(&rreq->list);
		buf_free(&rreq->data);
		g_free(rreq);
	}
	g_free(chunk);
}

static void chunk_put(struct read_chunk *chunk)
{
	if (chunk) {
		chunk->refs--;
		if (!chunk->refs)
			chunk_free(chunk);
	}
}

static void chunk_put_locked(struct read_chunk *chunk)
{
	pthread_mutex_lock(&sshfs.lock);
	chunk_put(chunk);
	pthread_mutex_unlock(&sshfs.lock);
}

static int clean_req(void *key, struct request *req, gpointer user_data)
{
	(void) key;
	struct conn* conn = (struct conn*) user_data;

	if (req->conn != conn)
		return FALSE;

	req->error = -EIO;
	if (req->want_reply)
		sem_post(&req->ready);
	else
		request_free(req);

	return TRUE;
}

static int process_one_request(struct conn *conn)
{
	int res;
	struct buffer buf;
	uint8_t type;
	struct request *req;
	uint32_t id;

	buf_init(&buf, 0);
	res = sftp_read(conn, &type, &buf);
	if (res == -1)
		return -1;
	if (buf_get_uint32(&buf, &id) == -1)
		return -1;

	pthread_mutex_lock(&sshfs.lock);
	req = (struct request *)
		g_hash_table_lookup(sshfs.reqtab, GUINT_TO_POINTER(id));
	if (req == NULL)
		fprintf(stderr, "request %i not found\n", id);
	else {
		int was_over;

		was_over = sshfs.outstanding_len > sshfs.max_outstanding_len;
		sshfs.outstanding_len -= req->len;
		if (was_over &&
		    sshfs.outstanding_len <= sshfs.max_outstanding_len) {
			pthread_cond_broadcast(&sshfs.outstanding_cond);
		}
		g_hash_table_remove(sshfs.reqtab, GUINT_TO_POINTER(id));
	}
	pthread_mutex_unlock(&sshfs.lock);
	if (req != NULL) {
		if (sshfs.debug) {
			struct timeval now;
			unsigned int difftime;
			unsigned msgsize = buf.size + 5;

			gettimeofday(&now, NULL);
			difftime = (now.tv_sec - req->start.tv_sec) * 1000;
			difftime += (now.tv_usec - req->start.tv_usec) / 1000;
			DEBUG("  [%05i] %14s %8ubytes (%ims)\n", id,
			      type_name(type), msgsize, difftime);

			if (difftime < sshfs.min_rtt || !sshfs.num_received)
				sshfs.min_rtt = difftime;
			if (difftime > sshfs.max_rtt)
				sshfs.max_rtt = difftime;
			sshfs.total_rtt += difftime;
			sshfs.num_received++;
			sshfs.bytes_received += msgsize;
		}
		req->reply = buf;
		req->reply_type = type;
		req->replied = 1;
		if (req->want_reply)
			sem_post(&req->ready);
		else {
			pthread_mutex_lock(&sshfs.lock);
			request_free(req);
			pthread_mutex_unlock(&sshfs.lock);
		}
	} else
		buf_free(&buf);

	return 0;
}

static void close_conn(struct conn *conn)
{
	close(conn->rfd);
	if (conn->rfd != conn->wfd)
		close(conn->wfd);
	conn->rfd = -1;
	conn->wfd = -1;
	if (sshfs.ptyfd != -1) {
		close(sshfs.ptyfd);
		sshfs.ptyfd = -1;
	}
	if (sshfs.ptypassivefd != -1) {
		close(sshfs.ptypassivefd);
		sshfs.ptypassivefd = -1;
	}
}

static void *process_requests(void *data_)
{
	(void) data_;
	struct conn *conn = data_;

	while (1) {
		if (process_one_request(conn) == -1)
			break;
	}

	pthread_mutex_lock(&sshfs.lock);
	conn->processing_thread_started = 0;
	close_conn(conn);
	g_hash_table_foreach_remove(sshfs.reqtab, (GHRFunc) clean_req, conn);
	conn->connver = ++sshfs.connvers;
	sshfs.outstanding_len = 0;
	pthread_cond_broadcast(&sshfs.outstanding_cond);
	pthread_mutex_unlock(&sshfs.lock);

	if (!sshfs.reconnect) {
		/* harakiri */
		kill(getpid(), SIGTERM);
	}
	return NULL;
}

static int sftp_init_reply_ok(struct conn *conn, struct buffer *buf,
                              uint32_t *version)
{
	uint32_t len;
	uint8_t type;

	if (buf_get_uint32(buf, &len) == -1)
		return -1;

	if (len < 5 || len > MAX_REPLY_LEN)
		return 1;

	if (buf_get_uint8(buf, &type) == -1)
		return -1;

	if (type != SSH_FXP_VERSION)
		return 1;

	if (buf_get_uint32(buf, version) == -1)
		return -1;

	DEBUG("Server version: %u\n", *version);

	if (len > 5) {
		struct buffer buf2;

		buf_init(&buf2, len - 5);
		if (do_read(conn, &buf2) == -1) {
			buf_free(&buf2);
			return -1;
		}

		do {
			char *ext = NULL;
			char *extdata = NULL;

			if (buf_get_string(&buf2, &ext) == -1 ||
			    buf_get_string(&buf2, &extdata) == -1) {
				buf_free(&buf2);
				free(ext);
				free(extdata);
				return -1;
			}

			DEBUG("Extension: %s <%s>\n", ext, extdata);

			if (strcmp(ext, SFTP_EXT_POSIX_RENAME) == 0 &&
			    strcmp(extdata, "1") == 0) {
				sshfs.ext_posix_rename = 1;
				sshfs.rename_workaround = 0;
			}
			if (strcmp(ext, SFTP_EXT_STATVFS) == 0 &&
			    strcmp(extdata, "2") == 0)
				sshfs.ext_statvfs = 1;
			if (strcmp(ext, SFTP_EXT_HARDLINK) == 0 &&
			    strcmp(extdata, "1") == 0)
				sshfs.ext_hardlink = 1;
			if (strcmp(ext, SFTP_EXT_FSYNC) == 0 &&
			    strcmp(extdata, "1") == 0)
				sshfs.ext_fsync = 1;

			free(ext);
			free(extdata);
		} while (buf2.len < buf2.size);
		buf_free(&buf2);
	}
	return 0;
}

static int sftp_find_init_reply(struct conn *conn, uint32_t *version)
{
	int res;
	struct buffer buf;

	buf_init(&buf, 9);
	res = do_read(conn, &buf);
	while (res != -1) {
		struct buffer buf2;

		res = sftp_init_reply_ok(conn, &buf, version);
		if (res <= 0)
			break;

		/* Iterate over any rubbish until the version reply is found */
		DEBUG("%c", *buf.p);
		memmove(buf.p, buf.p + 1, buf.size - 1);
		buf.len = 0;
		buf2.p = buf.p + buf.size - 1;
		buf2.size = 1;
		res = do_read(conn, &buf2);
	}
	buf_free(&buf);
	return res;
}

static int sftp_init(struct conn *conn)
{
	int res = -1;
	uint32_t version = 0;
	struct buffer buf;
	buf_init(&buf, 0);
	if (sftp_send_iov(conn, SSH_FXP_INIT, PROTO_VERSION, NULL, 0) == -1)
		goto out;

	if (sshfs.password_stdin && pty_expect_loop(conn) == -1)
		goto out;

	if (sftp_find_init_reply(conn, &version) == -1)
		goto out;

	sshfs.server_version = version;
	if (version > PROTO_VERSION) {
		fprintf(stderr,
			"Warning: server uses version: %i, we support: %i\n",
			version, PROTO_VERSION);
	}
	res = 0;

out:
	buf_free(&buf);
	return res;
}

static int sftp_error_to_errno(uint32_t error)
{
	switch (error) {
	case SSH_FX_OK:                return 0;
	case SSH_FX_NO_SUCH_FILE:      return ENOENT;
	case SSH_FX_PERMISSION_DENIED: return EACCES;
	case SSH_FX_FAILURE:           return EPERM;
	case SSH_FX_BAD_MESSAGE:       return EBADMSG;
	case SSH_FX_NO_CONNECTION:     return ENOTCONN;
	case SSH_FX_CONNECTION_LOST:   return ECONNABORTED;
	case SSH_FX_OP_UNSUPPORTED:    return EOPNOTSUPP;
	default:                       return EIO;
	}
}

static void sftp_detect_uid(struct conn *conn)
{
	int flags;
	uint32_t id = sftp_get_id();
	uint32_t replid;
	uint8_t type;
	struct buffer buf;
	struct stat stbuf;
	struct iovec iov[1];

	buf_init(&buf, 5);
	buf_add_string(&buf, ".");
	buf_to_iov(&buf, &iov[0]);
	if (sftp_send_iov(conn, SSH_FXP_STAT, id, iov, 1) == -1)
		goto out;
	buf_clear(&buf);
	if (sftp_read(conn, &type, &buf) == -1)
		goto out;
	if (type != SSH_FXP_ATTRS && type != SSH_FXP_STATUS) {
		fprintf(stderr, "protocol error\n");
		goto out;
	}
	if (buf_get_uint32(&buf, &replid) == -1)
		goto out;
	if (replid != id) {
		fprintf(stderr, "bad reply ID\n");
		goto out;
	}
	if (type == SSH_FXP_STATUS) {
		uint32_t serr;
		if (buf_get_uint32(&buf, &serr) == -1)
			goto out;

		fprintf(stderr, "failed to stat home directory (%i)\n", serr);
		goto out;
	}
	if (buf_get_attrs(&buf, &stbuf, &flags) != 0)
		goto out;

	if (!(flags & SSH_FILEXFER_ATTR_UIDGID))
		goto out;

	sshfs.remote_uid = stbuf.st_uid;
	sshfs.local_uid = getuid();
	sshfs.remote_gid = stbuf.st_gid;
	sshfs.local_gid = getgid();
	sshfs.remote_uid_detected = 1;
	DEBUG("remote_uid = %i\n", sshfs.remote_uid);

out:
	if (!sshfs.remote_uid_detected)
		fprintf(stderr, "failed to detect remote user ID\n");

	buf_free(&buf);
}

static int sftp_check_root(struct conn *conn, const char *base_path)
{
	int flags;
	uint32_t id = sftp_get_id();
	uint32_t replid;
	uint8_t type;
	struct buffer buf;
	struct stat stbuf;
	struct iovec iov[1];
	int err = -1;
	const char *remote_dir = base_path[0] ? base_path : ".";

	buf_init(&buf, 0);
	buf_add_string(&buf, remote_dir);
	buf_to_iov(&buf, &iov[0]);
	if (sftp_send_iov(conn, SSH_FXP_LSTAT, id, iov, 1) == -1)
		goto out;
	buf_clear(&buf);
	if (sftp_read(conn, &type, &buf) == -1)
		goto out;
	if (type != SSH_FXP_ATTRS && type != SSH_FXP_STATUS) {
		fprintf(stderr, "protocol error\n");
		goto out;
	}
	if (buf_get_uint32(&buf, &replid) == -1)
		goto out;
	if (replid != id) {
		fprintf(stderr, "bad reply ID\n");
		goto out;
	}
	if (type == SSH_FXP_STATUS) {
		uint32_t serr;
		if (buf_get_uint32(&buf, &serr) == -1)
			goto out;

		fprintf(stderr, "%s:%s: %s\n", sshfs.host, remote_dir,
			strerror(sftp_error_to_errno(serr)));

		goto out;
	}

	int err2 = buf_get_attrs(&buf, &stbuf, &flags);
	if (err2) {
		err = err2;
		goto out;
	}

	if (!(flags & SSH_FILEXFER_ATTR_PERMISSIONS))
		goto out;

	if (!S_ISDIR(stbuf.st_mode)) {
		fprintf(stderr, "%s:%s: Not a directory\n", sshfs.host,
			remote_dir);
		goto out;
	}

	err = 0;

out:
	buf_free(&buf);
	return err;
}

static int connect_remote(struct conn *conn)
{
	int err;

	if (sshfs.passive)
		err = connect_passive(conn);
	else if (sshfs.directport)
		err = connect_to(conn, sshfs.host, sshfs.directport);
	else if (sshfs.vsock)
		err = connect_vsock(conn, sshfs.vsock);
	else
		err = start_ssh(conn);
	if (!err)
		err = sftp_init(conn);

	if (err)
		close_conn(conn);
	else
		sshfs.num_connect++;

	return err;
}

static int start_processing_thread(struct conn *conn)
{
	int err;
	pthread_t thread_id;
	sigset_t oldset;
	sigset_t newset;

	if (conn->processing_thread_started)
		return 0;

	if (conn->rfd == -1) {
		err = connect_remote(conn);
		if (err)
			return -EIO;
	}

	if (sshfs.detect_uid) {
		sftp_detect_uid(conn);
		sshfs.detect_uid = 0;
	}

	sigemptyset(&newset);
	sigaddset(&newset, SIGTERM);
	sigaddset(&newset, SIGINT);
	sigaddset(&newset, SIGHUP);
	sigaddset(&newset, SIGQUIT);
	pthread_sigmask(SIG_BLOCK, &newset, &oldset);
	err = pthread_create(&thread_id, NULL, process_requests, conn);
	if (err) {
		fprintf(stderr, "failed to create thread: %s\n", strerror(err));
		return -EIO;
	}
	pthread_detach(thread_id);
	pthread_sigmask(SIG_SETMASK, &oldset, NULL);
	conn->processing_thread_started = 1;
	return 0;
}

static void *sshfs_init(struct fuse_conn_info *conn,
                        struct fuse_config *cfg)
{
	/* Readahead should be done by kernel or sshfs but not both */
	if (conn->capable & FUSE_CAP_ASYNC_READ)
		sshfs.sync_read = 1;

	// These workarounds require the "path" argument.
	cfg->nullpath_ok = !(sshfs.truncate_workaround || sshfs.fstat_workaround);

	// When using multiple connections, release() needs to know the path
	if (sshfs.max_conns > 1)
		cfg->nullpath_ok = 0;

	// Lookup of . and .. is supported
	conn->capable |= FUSE_CAP_EXPORT_SUPPORT;

	if (!sshfs.delay_connect)
		start_processing_thread(&sshfs.conns[0]);

	// SFTP only supports 1-second time resolution
	conn->time_gran = 1000000000;

	return NULL;
}

static int sftp_request_wait(struct request *req, uint8_t type,
                             uint8_t expect_type, struct buffer *outbuf)
{
	int err;

	if (req->error) {
		err = req->error;
		goto out;
	}
	while (sem_wait(&req->ready));
	if (req->error) {
		err = req->error;
		goto out;
	}
	err = -EIO;
	if (req->reply_type != expect_type &&
	    req->reply_type != SSH_FXP_STATUS) {
		fprintf(stderr, "protocol error\n");
		goto out;
	}
	if (req->reply_type == SSH_FXP_STATUS) {
		uint32_t serr;
		if (buf_get_uint32(&req->reply, &serr) == -1)
			goto out;

		switch (serr) {
		case SSH_FX_OK:
			if (expect_type == SSH_FXP_STATUS)
				err = 0;
			else
				err = -EIO;
			break;

		case SSH_FX_EOF:
			if (type == SSH_FXP_READ || type == SSH_FXP_READDIR)
				err = MY_EOF;
			else
				err = -EIO;
			break;

		case SSH_FX_FAILURE:
			if (type == SSH_FXP_RMDIR)
				err = -ENOTEMPTY;
			else
				err = -EPERM;
			break;

		default:
			err = -sftp_error_to_errno(serr);
		}
	} else {
		buf_init(outbuf, req->reply.size - req->reply.len);
		buf_get_mem(&req->reply, outbuf->p, outbuf->size);
		err = 0;
	}

out:
	pthread_mutex_lock(&sshfs.lock);
	request_free(req);
	pthread_mutex_unlock(&sshfs.lock);
	return err;
}

static int sftp_request_send(struct conn *conn, uint8_t type, struct iovec *iov,
			     size_t count, request_func begin_func, request_func end_func,
			     int want_reply, void *data, struct request **reqp)
{
	int err;
	uint32_t id;
	struct request *req = g_new0(struct request, 1);

	req->want_reply = want_reply;
	req->end_func = end_func;
	req->data = data;
	sem_init(&req->ready, 0, 0);
	buf_init(&req->reply, 0);
	pthread_mutex_lock(&sshfs.lock);
	if (begin_func)
		begin_func(req);
	id = sftp_get_id();
	req->id = id;
	req->conn = conn;
	req->conn->req_count++;
	err = start_processing_thread(conn);
	if (err) {
		pthread_mutex_unlock(&sshfs.lock);
		goto out;
	}
	req->len = iov_length(iov, count) + 9;
	sshfs.outstanding_len += req->len;
	while (sshfs.outstanding_len > sshfs.max_outstanding_len)
		pthread_cond_wait(&sshfs.outstanding_cond, &sshfs.lock);

	g_hash_table_insert(sshfs.reqtab, GUINT_TO_POINTER(id), req);
	if (sshfs.debug) {
		gettimeofday(&req->start, NULL);
		sshfs.num_sent++;
		sshfs.bytes_sent += req->len;
	}
	DEBUG("[%05i] %s\n", id, type_name(type));
	pthread_mutex_unlock(&sshfs.lock);

	err = -EIO;
	if (sftp_send_iov(conn, type, id, iov, count) == -1) {
		gboolean rmed;

		pthread_mutex_lock(&sshfs.lock);
		rmed = g_hash_table_remove(sshfs.reqtab, GUINT_TO_POINTER(id));
		pthread_mutex_unlock(&sshfs.lock);

		if (!rmed && !want_reply) {
			/* request already freed */
			return err;
		}
		goto out;
	}
	if (want_reply)
		*reqp = req;
	return 0;

out:
	req->error = err;
	if (!want_reply)
		sftp_request_wait(req, type, 0, NULL);
	else
		*reqp = req;

	return err;
}

static int sftp_request_iov(struct conn *conn, uint8_t type, struct iovec *iov,
			    size_t count, uint8_t expect_type, struct buffer *outbuf)
{
	int err;
	struct request *req;

	err = sftp_request_send(conn, type, iov, count, NULL, NULL,
				expect_type, NULL, &req);
	if (expect_type == 0)
		return err;

	return sftp_request_wait(req, type, expect_type, outbuf);
}

static int sftp_request(struct conn *conn, uint8_t type, const struct buffer *buf,
			uint8_t expect_type, struct buffer *outbuf)
{
	struct iovec iov;

	buf_to_iov(buf, &iov);
	return sftp_request_iov(conn, type, &iov, 1, expect_type, outbuf);
}

static int sshfs_access(const char *path, int mask)
{
	struct stat stbuf;
	int err = 0;

	if (mask & X_OK) {
		err = sshfs.op->getattr(path, &stbuf, NULL);
		if (!err) {
			if (S_ISREG(stbuf.st_mode) &&
			    !(stbuf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))
				err = -EACCES;
		}
	}
	return err;
}

static int count_components(const char *p)
{
	int ctr;

	for (; *p == '/'; p++);
	for (ctr = 0; *p; ctr++) {
		for (; *p && *p != '/'; p++);
		for (; *p == '/'; p++);
	}
	return ctr;
}

static void strip_common(const char **sp, const char **tp)
{
	const char *s = *sp;
	const char *t = *tp;
	do {
		for (; *s == '/'; s++);
		for (; *t == '/'; t++);
		*tp = t;
		*sp = s;
		for (; *s == *t && *s && *s != '/'; s++, t++);
	} while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
}

static void transform_symlink(const char *path, char **linkp)
{
	const char *l = *linkp;
	const char *b = sshfs.base_path;
	char *newlink;
	char *s;
	int dotdots;
	int i;

	if (l[0] != '/' || b[0] != '/')
		return;

	strip_common(&l, &b);
	if (*b)
		return;

	strip_common(&l, &path);
	dotdots = count_components(path);
	if (!dotdots)
		return;
	dotdots--;

	newlink = malloc(dotdots * 3 + strlen(l) + 2);
	if (!newlink) {
		fprintf(stderr, "sshfs: memory allocation failed\n");
		abort();
	}
	for (s = newlink, i = 0; i < dotdots; i++, s += 3)
		strcpy(s, "../");

	if (l[0])
		strcpy(s, l);
	else if (!dotdots)
		strcpy(s, ".");
	else
		s[0] = '\0';

	free(*linkp);
	*linkp = newlink;
}

static int sshfs_readlink(const char *path, char *linkbuf, size_t size)
{
	int err;
	struct buffer buf;
	struct buffer name;

	assert(size > 0);

	if (sshfs.server_version < 3)
		return -EPERM;

	buf_init(&buf, 0);
	buf_add_path(&buf, path);
	// Commutes with pending write(), so we can use any connection
	err = sftp_request(get_conn(NULL, NULL), SSH_FXP_READLINK, &buf, SSH_FXP_NAME, &name);
	if (!err) {
		uint32_t count;
		char *link;
		err = -EIO;
		if(buf_get_uint32(&name, &count) != -1 && count == 1 &&
		   buf_get_string(&name, &link) != -1) {
			if (sshfs.transform_symlinks)
				transform_symlink(path, &link);
			strncpy(linkbuf, link, size - 1);
			linkbuf[size - 1] = '\0';
			free(link);
			err = 0;
		}
		buf_free(&name);
	}
	buf_free(&buf);
	return err;
}

static int sftp_readdir_send(struct conn *conn, struct request **req,
			     struct buffer *handle)
{
	struct iovec iov;

	buf_to_iov(handle, &iov);
	return sftp_request_send(conn, SSH_FXP_READDIR, &iov, 1, NULL, NULL,
				 SSH_FXP_NAME, NULL, req);
}

static int sshfs_req_pending(struct request *req)
{
	if (g_hash_table_lookup(sshfs.reqtab, GUINT_TO_POINTER(req->id)))
		return 1;
	else
		return 0;
}

static int sftp_readdir_async(struct conn *conn, struct buffer *handle,
			      void *buf, off_t offset, fuse_fill_dir_t filler)
{
	int err = 0;
	int outstanding = 0;
	int max = READDIR_START;
	GList *list = NULL;

	int done = 0;

	assert(offset == 0);
	while (!done || outstanding) {
		struct request *req;
		struct buffer name;
		int tmperr;

		while (!done && outstanding < max) {
			tmperr = sftp_readdir_send(conn, &req, handle);

			if (tmperr && !done) {
				err = tmperr;
				done = 1;
				break;
			}

			list = g_list_append(list, req);
			outstanding++;
		}

		if (outstanding) {
			GList *first;
			/* wait for response to next request */
			first = g_list_first(list);
			req = first->data;
			list = g_list_delete_link(list, first);
			outstanding--;

			if (done) {
				/* We need to cache want_reply, since processing
				   thread may free req right after unlock() if
				   want_reply == 0 */
				int want_reply;
				pthread_mutex_lock(&sshfs.lock);
				if (sshfs_req_pending(req))
					req->want_reply = 0;
				want_reply = req->want_reply;
				pthread_mutex_unlock(&sshfs.lock);
				if (!want_reply)
					continue;
			}

			tmperr = sftp_request_wait(req, SSH_FXP_READDIR,
						    SSH_FXP_NAME, &name);

			if (tmperr && !done) {
				err = tmperr;
				if (err == MY_EOF)
					err = 0;
				done = 1;
			}
			if (!done) {
				err = buf_get_entries(&name, buf, filler);
				buf_free(&name);

				/* increase number of outstanding requests */
				if (max < READDIR_MAX)
					max++;

				if (err)
					done = 1;
			}
		}
	}
	assert(list == NULL);

	return err;
}

static int sftp_readdir_sync(struct conn *conn, struct buffer *handle,
			     void *buf, off_t offset, fuse_fill_dir_t filler)
{
	int err;
	assert(offset == 0);
	do {
		struct buffer name;
		err = sftp_request(conn, SSH_FXP_READDIR, handle, SSH_FXP_NAME, &name);
		if (!err) {
			err = buf_get_entries(&name, buf, filler);
			buf_free(&name);
		}
	} while (!err);
	if (err == MY_EOF)
		err = 0;

	return err;
}

static int sshfs_opendir(const char *path, struct fuse_file_info *fi)
{
	int err;
	struct conn *conn;
	struct buffer buf;
	struct dir_handle *handle;

	handle = g_new0(struct dir_handle, 1);
	if(handle == NULL)
		return -ENOMEM;

	// Commutes with pending write(), so we can use any connection
	conn = get_conn(NULL, NULL);
	buf_init(&buf, 0);
	buf_add_path(&buf, path);
	err = sftp_request(conn, SSH_FXP_OPENDIR, &buf, SSH_FXP_HANDLE, &handle->buf);
	if (!err) {
		buf_finish(&handle->buf);
		pthread_mutex_lock(&sshfs.lock);
		handle->conn = conn;
		handle->conn->dir_count++;
		pthread_mutex_unlock(&sshfs.lock);
		fi->fh = (unsigned long) handle;
	} else
		g_free(handle);
	buf_free(&buf);
	return err;
}

static int sshfs_readdir(const char *path, void *dbuf, fuse_fill_dir_t filler,
			 off_t offset, struct fuse_file_info *fi,
			 enum fuse_readdir_flags flags)
{
	(void) path; (void) flags;
	int err;
	struct dir_handle *handle;

	handle = (struct dir_handle*) fi->fh;

	if (sshfs.sync_readdir)
		err = sftp_readdir_sync(handle->conn, &handle->buf, dbuf,
					offset, filler);
	else
		err = sftp_readdir_async(handle->conn, &handle->buf, dbuf,
					 offset, filler);

	return err;
}

static int sshfs_releasedir(const char *path, struct fuse_file_info *fi)
{
	(void) path;
	int err;
	struct dir_handle *handle;

	handle = (struct dir_handle*) fi->fh;
	err = sftp_request(handle->conn, SSH_FXP_CLOSE, &handle->buf, 0, NULL);
	pthread_mutex_lock(&sshfs.lock);
	handle->conn->dir_count--;
	pthread_mutex_unlock(&sshfs.lock);
	buf_free(&handle->buf);
	g_free(handle);
	return err;
}


static int sshfs_mkdir(const char *path, mode_t mode)
{
	int err;
	struct buffer buf;
	buf_init(&buf, 0);
	buf_add_path(&buf, path);
	buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS);
	buf_add_uint32(&buf, mode);
	// Commutes with pending write(), so we can use any connection
	err = sftp_request(get_conn(NULL, NULL), SSH_FXP_MKDIR, &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);

	if (err == -EPERM) {
		if (sshfs.op->access(path, R_OK) == 0) {
			return -EEXIST;
		}
	}

	return err;
}

static int sshfs_mknod(const char *path, mode_t mode, dev_t rdev)
{
	int err;
	struct conn *conn;
	struct buffer buf;
	struct buffer handle;
	(void) rdev;

	if ((mode & S_IFMT) != S_IFREG)
		return -EPERM;

	// Commutes with pending write(), so we can use any connection
	conn = get_conn(NULL, NULL);

	buf_init(&buf, 0);
	buf_add_path(&buf, path);
	buf_add_uint32(&buf, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_EXCL);
	buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS);
	buf_add_uint32(&buf, mode);
	err = sftp_request(conn, SSH_FXP_OPEN, &buf, SSH_FXP_HANDLE, &handle);
	if (!err) {
		int err2;
		buf_finish(&handle);
		err2 = sftp_request(conn, SSH_FXP_CLOSE, &handle, SSH_FXP_STATUS, NULL);
		if (!err)
			err = err2;
		buf_free(&handle);
	}
	buf_free(&buf);
	return err;
}

static int sshfs_symlink(const char *from, const char *to)
{
	int err;
	struct buffer buf;

	if (sshfs.server_version < 3)
		return -EPERM;

	/* openssh sftp server doesn't follow standard: link target and
	   link name are mixed up, so we must also be non-standard :( */
	buf_init(&buf, 0);
	buf_add_string(&buf, from);
	buf_add_path(&buf, to);
	// Commutes with pending write(), so we can use any connection
	err = sftp_request(get_conn(NULL, NULL), SSH_FXP_SYMLINK, &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);
	return err;
}

static int sshfs_unlink(const char *path)
{
	int err;
	struct buffer buf;
	buf_init(&buf, 0);
	buf_add_path(&buf, path);
	// Commutes with pending write(), so we can use any connection
	err = sftp_request(get_conn(NULL, NULL), SSH_FXP_REMOVE, &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);
	return err;
}

static int sshfs_rmdir(const char *path)
{
	int err;
	struct buffer buf;
	buf_init(&buf, 0);
	buf_add_path(&buf, path);
	// Commutes with pending write(), so we can use any connection
	err = sftp_request(get_conn(NULL, NULL), SSH_FXP_RMDIR, &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);
	return err;
}

static int sshfs_do_rename(const char *from, const char *to)
{
	int err;
	struct buffer buf;
	buf_init(&buf, 0);
	buf_add_path(&buf, from);
	buf_add_path(&buf, to);
	// Commutes with pending write(), so we can use any connection
	err = sftp_request(get_conn(NULL, NULL), SSH_FXP_RENAME, &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);
	return err;
}

static int sshfs_ext_posix_rename(const char *from, const char *to)
{
	int err;
	struct buffer buf;
	buf_init(&buf, 0);
	buf_add_string(&buf, SFTP_EXT_POSIX_RENAME);
	buf_add_path(&buf, from);
	buf_add_path(&buf, to);
	// Commutes with pending write(), so we can use any connection
	err = sftp_request(get_conn(NULL, NULL), SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);
	return err;
}

static void random_string(char *str, int length)
{
	int i;
	for (i = 0; i < length; i++)
		*str++ = (char)('0' + rand_r(&sshfs.randseed) % 10);
	*str = '\0';
}

static int sshfs_rename(const char *from, const char *to, unsigned int flags)
{
	int err;
	struct conntab_entry *ce;

	if(flags != 0)
		return -EINVAL;

	if (sshfs.ext_posix_rename)
		err = sshfs_ext_posix_rename(from, to);
	else
		err = sshfs_do_rename(from, to);
	if (err == -EPERM && sshfs.rename_workaround) {
		size_t tolen = strlen(to);
		if (tolen + RENAME_TEMP_CHARS < PATH_MAX) {
			int tmperr;
			char totmp[PATH_MAX];
			strcpy(totmp, to);
			random_string(totmp + tolen, RENAME_TEMP_CHARS);
			tmperr = sshfs_do_rename(to, totmp);
			if (!tmperr) {
				err = sshfs_do_rename(from, to);
				if (!err)
					err = sshfs_unlink(totmp);
				else
					sshfs_do_rename(totmp, to);
			}
		}
	}
	if (err == -EPERM && sshfs.renamexdev_workaround)
		err = -EXDEV;

	if (!err && sshfs.max_conns > 1) {
		pthread_mutex_lock(&sshfs.lock);
		ce = g_hash_table_lookup(sshfs.conntab, from);
		if (ce != NULL) {
			g_hash_table_replace(sshfs.conntab, g_strdup(to), ce);
			g_hash_table_remove(sshfs.conntab, from);
		}
		pthread_mutex_unlock(&sshfs.lock);
	}

	return err;
}

static int sshfs_link(const char *from, const char *to)
{
	int err = -ENOSYS;

	if (sshfs.ext_hardlink && !sshfs.disable_hardlink) {
		struct buffer buf;

		buf_init(&buf, 0);
		buf_add_string(&buf, SFTP_EXT_HARDLINK);
		buf_add_path(&buf, from);
		buf_add_path(&buf, to);
		// Commutes with pending write(), so we can use any connection
		err = sftp_request(get_conn(NULL, NULL), SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS,
				   NULL);
		buf_free(&buf);
	}

	return err;
}

static inline int sshfs_file_is_conn(struct sshfs_file *sf)
{
	int ret;

	pthread_mutex_lock(&sshfs.lock);
	ret = (sf->connver == sf->conn->connver);
	pthread_mutex_unlock(&sshfs.lock);

	return ret;
}

static inline struct sshfs_file *get_sshfs_file(struct fuse_file_info *fi)
{
	return (struct sshfs_file *) (uintptr_t) fi->fh;
}

static int sshfs_chmod(const char *path, mode_t mode,
                       struct fuse_file_info *fi)
{
	(void) fi;
	int err;
	struct buffer buf;
	struct sshfs_file *sf = NULL;

	if (fi != NULL) {
		sf = get_sshfs_file(fi);
		if (!sshfs_file_is_conn(sf))
			return -EIO;
	}

	buf_init(&buf, 0);
	if (sf == NULL)
		buf_add_path(&buf, path);
	else
		buf_add_buf(&buf, &sf->handle);

	buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS);
	buf_add_uint32(&buf, mode);

	/* FIXME: really needs LSETSTAT extension (debian Bug#640038) */
	// Commutes with pending write(), so we can use any connection
	// if the file is not open.
	err = sftp_request(get_conn(sf, NULL),
			   sf == NULL ? SSH_FXP_SETSTAT : SSH_FXP_FSETSTAT,
			   &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);
	return err;
}

static int sshfs_chown(const char *path, uid_t uid, gid_t gid,
                       struct fuse_file_info *fi)
{
	(void) fi;
	int err;
	struct buffer buf;
	struct sshfs_file *sf = NULL;

	if (fi != NULL) {
		sf = get_sshfs_file(fi);
		if (!sshfs_file_is_conn(sf))
			return -EIO;
	}

	if (sshfs.remote_uid_detected) {
		if (uid == sshfs.local_uid)
			uid = sshfs.remote_uid;
		if (gid == sshfs.local_gid)
			gid = sshfs.remote_gid;
	}
	if (sshfs.idmap == IDMAP_FILE && sshfs.r_uid_map)
		if(translate_id(&uid, sshfs.r_uid_map) == -1)
			return -EPERM;
	if (sshfs.idmap == IDMAP_FILE && sshfs.r_gid_map)
		if (translate_id(&gid, sshfs.r_gid_map) == -1)
			return -EPERM;

	buf_init(&buf, 0);
	if (sf == NULL)
		buf_add_path(&buf, path);
	else
		buf_add_buf(&buf, &sf->handle);
	buf_add_uint32(&buf, SSH_FILEXFER_ATTR_UIDGID);
	buf_add_uint32(&buf, uid);
	buf_add_uint32(&buf, gid);

	// Commutes with pending write(), so we can use any connection
	// if the file is not open.
	err = sftp_request(get_conn(sf, NULL),
			   sf == NULL ? SSH_FXP_SETSTAT : SSH_FXP_FSETSTAT,
			   &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);
	return err;
}

static int sshfs_truncate_workaround(const char *path, off_t size,
                                     struct fuse_file_info *fi);

static void sshfs_inc_modifver(void)
{
	pthread_mutex_lock(&sshfs.lock);
	sshfs.modifver++;
	pthread_mutex_unlock(&sshfs.lock);
}

static int sshfs_utimens(const char *path, const struct timespec tv[2],
			 struct fuse_file_info *fi)
{
	(void) fi;
	int err;
	struct buffer buf;
	struct sshfs_file *sf = NULL;
	time_t asec = tv[0].tv_sec, msec = tv[1].tv_sec;

	struct timeval now;
	gettimeofday(&now, NULL);
	if (asec == 0)
		asec = now.tv_sec;
	if (msec == 0)
		msec = now.tv_sec;

	if (fi != NULL) {
		sf = get_sshfs_file(fi);
		if (!sshfs_file_is_conn(sf))
			return -EIO;
	}

	buf_init(&buf, 0);
	if (sf == NULL)
		buf_add_path(&buf, path);
	else
		buf_add_buf(&buf, &sf->handle);
	buf_add_uint32(&buf, SSH_FILEXFER_ATTR_ACMODTIME);
	buf_add_uint32(&buf, asec);
	buf_add_uint32(&buf, msec);

	err = sftp_request(get_conn(sf, path),
			   sf == NULL ? SSH_FXP_SETSTAT : SSH_FXP_FSETSTAT,
			   &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);
	return err;
}

static int sshfs_open_common(const char *path, mode_t mode,
                             struct fuse_file_info *fi)
{
	int err;
	int err2;
	struct buffer buf;
	struct buffer outbuf;
	struct stat stbuf;
	struct sshfs_file *sf;
	struct request *open_req;
	struct conntab_entry *ce;
	uint32_t pflags = 0;
	struct iovec iov;
	uint8_t type;
	uint64_t wrctr = 0;

	if (sshfs.dir_cache)
		wrctr = cache_get_write_ctr();

	if (sshfs.direct_io)
		fi->direct_io = 1;

	if ((fi->flags & O_ACCMODE) == O_RDONLY)
		pflags = SSH_FXF_READ;
	else if((fi->flags & O_ACCMODE) == O_WRONLY)
		pflags = SSH_FXF_WRITE;
	else if ((fi->flags & O_ACCMODE) == O_RDWR)
		pflags = SSH_FXF_READ | SSH_FXF_WRITE;
	else
		return -EINVAL;

	if (fi->flags & O_CREAT)
		pflags |= SSH_FXF_CREAT;

	if (fi->flags & O_EXCL)
		pflags |= SSH_FXF_EXCL;

	if (fi->flags & O_TRUNC)
		pflags |= SSH_FXF_TRUNC;

	if (fi->flags & O_APPEND)
		pflags |= SSH_FXF_APPEND;

	sf = g_new0(struct sshfs_file, 1);
	list_init(&sf->write_reqs);
	pthread_cond_init(&sf->write_finished, NULL);
	/* Assume random read after open */
	sf->is_seq = 0;
	sf->next_pos = 0;
	pthread_mutex_lock(&sshfs.lock);
	sf->modifver= sshfs.modifver;
	if (sshfs.max_conns > 1) {
		ce = g_hash_table_lookup(sshfs.conntab, path);
		if (!ce) {
			ce = g_malloc(sizeof(struct conntab_entry));
			ce->refcount = 0;
			ce->conn = get_conn(NULL, NULL);
			g_hash_table_insert(sshfs.conntab, g_strdup(path), ce);
		}
		sf->conn = ce->conn;
		ce->refcount++;
		sf->conn->file_count++;
		assert(sf->conn->file_count > 0);
	} else {
		sf->conn = &sshfs.conns[0];
		ce = NULL; // only to silence compiler warning
	}
	sf->connver = sf->conn->connver;
	pthread_mutex_unlock(&sshfs.lock);
	buf_init(&buf, 0);
	buf_add_path(&buf, path);
	buf_add_uint32(&buf, pflags);
	buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS);
	buf_add_uint32(&buf, mode);
	buf_to_iov(&buf, &iov);
	sftp_request_send(sf->conn, SSH_FXP_OPEN, &iov, 1, NULL, NULL, 1, NULL,
			  &open_req);
	buf_clear(&buf);
	buf_add_path(&buf, path);
	type = sshfs.follow_symlinks ? SSH_FXP_STAT : SSH_FXP_LSTAT;
	err2 = sftp_request(sf->conn, type, &buf, SSH_FXP_ATTRS, &outbuf);
	if (!err2) {
		err2 = buf_get_attrs(&outbuf, &stbuf, NULL);
		buf_free(&outbuf);
	}
	err = sftp_request_wait(open_req, SSH_FXP_OPEN, SSH_FXP_HANDLE,
				&sf->handle);
	if (!err && err2) {
		buf_finish(&sf->handle);
		sftp_request(sf->conn, SSH_FXP_CLOSE, &sf->handle, 0, NULL);
		buf_free(&sf->handle);
		err = err2;
	}

	if (!err) {
		if (sshfs.dir_cache)
			cache_add_attr(path, &stbuf, wrctr);
		buf_finish(&sf->handle);
		fi->fh = (unsigned long) sf;
	} else {
		if (sshfs.dir_cache)
			cache_invalidate(path);
		if (sshfs.max_conns > 1) {
			pthread_mutex_lock(&sshfs.lock);
			sf->conn->file_count--;
			ce->refcount--;
			if(ce->refcount == 0) {
				g_hash_table_remove(sshfs.conntab, path);
				g_free(ce);
			}
			pthread_mutex_unlock(&sshfs.lock);
		}
		g_free(sf);
	}
	buf_free(&buf);
	return err;
}

static int sshfs_open(const char *path, struct fuse_file_info *fi)
{
	return sshfs_open_common(path, 0, fi);
}

static int sshfs_flush(const char *path, struct fuse_file_info *fi)
{
	int err;
	struct sshfs_file *sf = get_sshfs_file(fi);
	struct list_head write_reqs;
	struct list_head *curr_list;

	if (!sshfs_file_is_conn(sf))
		return -EIO;

	if (sshfs.sync_write)
		return 0;

	(void) path;
	pthread_mutex_lock(&sshfs.lock);
	if (!list_empty(&sf->write_reqs)) {
		curr_list = sf->write_reqs.prev;
		list_del(&sf->write_reqs);
		list_init(&sf->write_reqs);
		list_add(&write_reqs, curr_list);
		while (!list_empty(&write_reqs))
			pthread_cond_wait(&sf->write_finished, &sshfs.lock);
	}
	err = sf->write_error;
	sf->write_error = 0;
	pthread_mutex_unlock(&sshfs.lock);
	return err;
}

static int sshfs_fsync(const char *path, int isdatasync,
                       struct fuse_file_info *fi)
{
	int err;
	(void) isdatasync;

	err = sshfs_flush(path, fi);
	if (err)
		return err;

	if (!sshfs.ext_fsync)
		return err;

	struct buffer buf;
	struct sshfs_file *sf = get_sshfs_file(fi);
	buf_init(&buf, 0);
	buf_add_string(&buf, SFTP_EXT_FSYNC);
	buf_add_buf(&buf, &sf->handle);
	err = sftp_request(sf->conn, SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);
	return err;
}

static int sshfs_release(const char *path, struct fuse_file_info *fi)
{
	struct sshfs_file *sf = get_sshfs_file(fi);
	struct buffer *handle = &sf->handle;
	struct conntab_entry *ce;
	if (sshfs_file_is_conn(sf)) {
		sshfs_flush(path, fi);
		sftp_request(sf->conn, SSH_FXP_CLOSE, handle, 0, NULL);
	}
	buf_free(handle);
	chunk_put_locked(sf->readahead);
	if (sshfs.max_conns > 1) {
		pthread_mutex_lock(&sshfs.lock);
		sf->conn->file_count--;
		ce = g_hash_table_lookup(sshfs.conntab, path);
		ce->refcount--;
		if(ce->refcount == 0) {
			g_hash_table_remove(sshfs.conntab, path);
			g_free(ce);
		}
		pthread_mutex_unlock(&sshfs.lock);
	}
	g_free(sf);
	return 0;
}

static void sshfs_read_end(struct request *req)
{
	struct read_req *rreq = (struct read_req *) req->data;
	if (req->error)
		rreq->res = req->error;
	else if (req->replied) {
		rreq->res = -EIO;

		if (req->reply_type == SSH_FXP_STATUS) {
			uint32_t serr;
			if (buf_get_uint32(&req->reply, &serr) != -1) {
				if (serr == SSH_FX_EOF)
					rreq->res = 0;
				else
					rreq->res = -sftp_error_to_errno(serr);
			}
		} else if (req->reply_type == SSH_FXP_DATA) {
			uint32_t retsize;
			if (buf_get_uint32(&req->reply, &retsize) != -1) {
				if (retsize > rreq->size) {
					fprintf(stderr, "long read\n");
				} else if (buf_check_get(&req->reply, retsize) != -1) {
					rreq->res = retsize;
					rreq->data = req->reply;
					buf_init(&req->reply, 0);
				}
			}
		} else {
			fprintf(stderr, "protocol error\n");
		}
	} else {
		rreq->res = -EIO;
	}

	rreq->sio->num_reqs--;
	if (!rreq->sio->num_reqs)
		pthread_cond_broadcast(&rreq->sio->finished);
}

static void sshfs_read_begin(struct request *req)
{
	struct read_req *rreq = (struct read_req *) req->data;
	rreq->sio->num_reqs++;
}

static struct read_chunk *sshfs_send_read(struct sshfs_file *sf, size_t size,
					  off_t offset)
{
	struct read_chunk *chunk = g_new0(struct read_chunk, 1);
	struct buffer *handle = &sf->handle;

	pthread_cond_init(&chunk->sio.finished, NULL);
	list_init(&chunk->reqs);
	chunk->size = size;
	chunk->offset = offset;
	chunk->refs = 1;

	while (size) {
		int err;
		struct buffer buf;
		struct iovec iov[1];
		struct read_req *rreq;
		size_t bsize = size < sshfs.max_read ? size : sshfs.max_read;

		rreq = g_new0(struct read_req, 1);
		rreq->sio = &chunk->sio;
		rreq->size = bsize;
		buf_init(&rreq->data, 0);
		list_add(&rreq->list, &chunk->reqs);

		buf_init(&buf, 0);
		buf_add_buf(&buf, handle);
		buf_add_uint64(&buf, offset);
		buf_add_uint32(&buf, bsize);
		buf_to_iov(&buf, &iov[0]);
		err = sftp_request_send(sf->conn, SSH_FXP_READ, iov, 1,
					sshfs_read_begin,
					sshfs_read_end,
					0, rreq, NULL);

		buf_free(&buf);
		if (err)
			break;

		size -= bsize;
		offset += bsize;
	}

	return chunk;
}

static int wait_chunk(struct read_chunk *chunk, char *buf, size_t size)
{
	int res = 0;
	struct read_req *rreq;

	pthread_mutex_lock(&sshfs.lock);
	while (chunk->sio.num_reqs)
	       pthread_cond_wait(&chunk->sio.finished, &sshfs.lock);
	pthread_mutex_unlock(&sshfs.lock);


	if (chunk->sio.error) {
		if (chunk->sio.error != MY_EOF)
			res = chunk->sio.error;

		goto out;
	}

	while (!list_empty(&chunk->reqs) && size) {
		rreq = list_entry(chunk->reqs.prev, struct read_req, list);

		if (rreq->res < 0) {
			chunk->sio.error = rreq->res;
			break;
		} if (rreq->res == 0) {
			chunk->sio.error = MY_EOF;
			break;
		} else if (size < (size_t) rreq->res) {
			buf_get_mem(&rreq->data, buf, size);
			rreq->res -= size;
			rreq->size -= size;
			res += size;
			break;
		} else {
			buf_get_mem(&rreq->data, buf, rreq->res);
			res += rreq->res;
			if ((size_t) rreq->res < rreq->size) {
				chunk->sio.error = MY_EOF;
				break;
			}
			buf += rreq->res;
			size -= rreq->res;
			list_del(&rreq->list);
			buf_free(&rreq->data);
			g_free(rreq);
		}
	}

	if (res > 0) {
		chunk->offset += res;
		chunk->size -= res;
	}

out:
	chunk_put_locked(chunk);
	return res;
}

static int sshfs_sync_read(struct sshfs_file *sf, char *buf, size_t size,
                           off_t offset)
{
	struct read_chunk *chunk;

	chunk = sshfs_send_read(sf, size, offset);
	return wait_chunk(chunk, buf, size);
}

static void submit_read(struct sshfs_file *sf, size_t size, off_t offset,
                        struct read_chunk **chunkp)
{
	struct read_chunk *chunk;

	chunk = sshfs_send_read(sf, size, offset);
	pthread_mutex_lock(&sshfs.lock);
	chunk->modifver = sshfs.modifver;
	chunk_put(*chunkp);
	*chunkp = chunk;
	chunk->refs++;
	pthread_mutex_unlock(&sshfs.lock);
}

static struct read_chunk *search_read_chunk(struct sshfs_file *sf, off_t offset)
{
	struct read_chunk *ch = sf->readahead;
	if (ch && ch->offset == offset && ch->modifver == sshfs.modifver) {
		ch->refs++;
		return ch;
	} else
		return NULL;
}

static int sshfs_async_read(struct sshfs_file *sf, char *rbuf, size_t size,
                            off_t offset)
{
	int res = 0;
	size_t total = 0;
	struct read_chunk *chunk;
	struct read_chunk *chunk_prev = NULL;
	size_t origsize = size;
	int curr_is_seq;

	pthread_mutex_lock(&sshfs.lock);
	curr_is_seq = sf->is_seq;
	sf->is_seq = (sf->next_pos == offset && sf->modifver == sshfs.modifver);
	sf->next_pos = offset + size;
	sf->modifver = sshfs.modifver;
	chunk = search_read_chunk(sf, offset);
	pthread_mutex_unlock(&sshfs.lock);

	if (chunk && chunk->size < size) {
		chunk_prev = chunk;
		size -= chunk->size;
		offset += chunk->size;
		chunk = NULL;
	}

	if (!chunk)
		submit_read(sf, size, offset, &chunk);

	if (curr_is_seq && chunk && chunk->size <= size)
		submit_read(sf, origsize, offset + size, &sf->readahead);

	if (chunk_prev) {
		size_t prev_size = chunk_prev->size;
		res = wait_chunk(chunk_prev, rbuf, prev_size);
		if (res < (int) prev_size) {
			chunk_put_locked(chunk);
			return res;
		}
		rbuf += res;
		total += res;
	}
	res = wait_chunk(chunk, rbuf, size);
	if (res > 0)
		total += res;
	if (res < 0)
		return res;

	return total;
}

static int sshfs_read(const char *path, char *rbuf, size_t size, off_t offset,
                      struct fuse_file_info *fi)
{
	struct sshfs_file *sf = get_sshfs_file(fi);
	(void) path;

	if (!sshfs_file_is_conn(sf))
		return -EIO;

	if (sshfs.sync_read)
		return sshfs_sync_read(sf, rbuf, size, offset);
	else
		return sshfs_async_read(sf, rbuf, size, offset);
}

static void sshfs_write_begin(struct request *req)
{
	struct sshfs_file *sf = (struct sshfs_file *) req->data;
	list_add(&req->list, &sf->write_reqs);
}

static void sshfs_write_end(struct request *req)
{
	uint32_t serr;
	struct sshfs_file *sf = (struct sshfs_file *) req->data;

	if (req->error)
		sf->write_error = req->error;
	else if (req->replied) {
		if (req->reply_type != SSH_FXP_STATUS) {
			fprintf(stderr, "protocol error\n");
		} else if (buf_get_uint32(&req->reply, &serr) != -1 &&
			serr != SSH_FX_OK) {
			sf->write_error = -EIO;
		}
	}
	list_del(&req->list);
	pthread_cond_broadcast(&sf->write_finished);
}

static int sshfs_async_write(struct sshfs_file *sf, const char *wbuf,
			     size_t size, off_t offset)
{
	int err = 0;
	struct buffer *handle = &sf->handle;

	while (!err && size) {
		struct buffer buf;
		struct iovec iov[2];
		size_t bsize = size < sshfs.max_write ? size : sshfs.max_write;

		buf_init(&buf, 0);
		buf_add_buf(&buf, handle);
		buf_add_uint64(&buf, offset);
		buf_add_uint32(&buf, bsize);
		buf_to_iov(&buf, &iov[0]);
		iov[1].iov_base = (void *) wbuf;
		iov[1].iov_len = bsize;
		err = sftp_request_send(sf->conn, SSH_FXP_WRITE, iov, 2,
					sshfs_write_begin, sshfs_write_end,
					0, sf, NULL);
		buf_free(&buf);
		size -= bsize;
		wbuf += bsize;
		offset += bsize;
	}

	return err;
}

static void sshfs_sync_write_begin(struct request *req)
{
	struct sshfs_io *sio = (struct sshfs_io *) req->data;
	sio->num_reqs++;
}

static void sshfs_sync_write_end(struct request *req)
{
	uint32_t serr;
	struct sshfs_io *sio = (struct sshfs_io *) req->data;

	if (req->error) {
		sio->error = req->error;
	} else if (req->replied) {
		if (req->reply_type != SSH_FXP_STATUS) {
			fprintf(stderr, "protocol error\n");
		} else if (buf_get_uint32(&req->reply, &serr) != -1 &&
			serr != SSH_FX_OK) {
			sio->error = -EIO;
		}
	}
	sio->num_reqs--;
	if (!sio->num_reqs)
		pthread_cond_broadcast(&sio->finished);
}


static int sshfs_sync_write(struct sshfs_file *sf, const char *wbuf,
			    size_t size, off_t offset)
{
	int err = 0;
	struct buffer *handle = &sf->handle;
	struct sshfs_io sio = { .error = 0, .num_reqs = 0 };

	pthread_cond_init(&sio.finished, NULL);

	while (!err && size) {
		struct buffer buf;
		struct iovec iov[2];
		size_t bsize = size < sshfs.max_write ? size : sshfs.max_write;

		buf_init(&buf, 0);
		buf_add_buf(&buf, handle);
		buf_add_uint64(&buf, offset);
		buf_add_uint32(&buf, bsize);
		buf_to_iov(&buf, &iov[0]);
		iov[1].iov_base = (void *) wbuf;
		iov[1].iov_len = bsize;
		err = sftp_request_send(sf->conn, SSH_FXP_WRITE, iov, 2,
					sshfs_sync_write_begin,
					sshfs_sync_write_end,
					0, &sio, NULL);
		buf_free(&buf);
		size -= bsize;
		wbuf += bsize;
		offset += bsize;
	}

	pthread_mutex_lock(&sshfs.lock);
	while (sio.num_reqs)
	       pthread_cond_wait(&sio.finished, &sshfs.lock);
	pthread_mutex_unlock(&sshfs.lock);

	if (!err)
		err = sio.error;

	return err;
}

static int sshfs_write(const char *path, const char *wbuf, size_t size,
                       off_t offset, struct fuse_file_info *fi)
{
	int err;
	struct sshfs_file *sf = get_sshfs_file(fi);

	(void) path;

	if (!sshfs_file_is_conn(sf))
		return -EIO;

	sshfs_inc_modifver();

	if (!sshfs.sync_write && !sf->write_error)
		err = sshfs_async_write(sf, wbuf, size, offset);
	else
		err = sshfs_sync_write(sf, wbuf, size, offset);

	return err ? err : (int) size;
}

static int sshfs_ext_statvfs(const char *path, struct statvfs *stbuf)
{
	int err;
	struct buffer buf;
	struct buffer outbuf;
	buf_init(&buf, 0);
	buf_add_string(&buf, SFTP_EXT_STATVFS);
	buf_add_path(&buf, path);
	err = sftp_request(get_conn(NULL, NULL), SSH_FXP_EXTENDED, &buf,
			   SSH_FXP_EXTENDED_REPLY, &outbuf);
	if (!err) {
		if (buf_get_statvfs(&outbuf, stbuf) == -1)
			err = -EIO;
		buf_free(&outbuf);
	}
	buf_free(&buf);
	return err;
}


static int sshfs_statfs(const char *path, struct statvfs *buf)
{
	if (sshfs.ext_statvfs)
		return sshfs_ext_statvfs(path, buf);

	buf->f_namemax = 255;
	buf->f_bsize = sshfs.blksize;
	/*
	 * df seems to use f_bsize instead of f_frsize, so make them
	 * the same
	 */
	buf->f_frsize = buf->f_bsize;
	buf->f_blocks = buf->f_bfree =  buf->f_bavail =
		1000ULL * 1024 * 1024 * 1024 / buf->f_frsize;
	buf->f_files = buf->f_ffree = 1000000000;
	return 0;
}

static int sshfs_create(const char *path, mode_t mode,
                        struct fuse_file_info *fi)
{
	if (sshfs.createmode_workaround)
		mode = 0;

	return sshfs_open_common(path, mode, fi);
}

static int sshfs_truncate(const char *path, off_t size,
			  struct fuse_file_info *fi)
{
	int err;
	struct buffer buf;
	struct sshfs_file *sf = NULL;

	if (fi != NULL) {
		sf = get_sshfs_file(fi);
		if (!sshfs_file_is_conn(sf))
			return -EIO;
	}

	sshfs_inc_modifver();
	if (sshfs.truncate_workaround)
		return sshfs_truncate_workaround(path, size, fi);

	buf_init(&buf, 0);

	if (sf != NULL)
		buf_add_buf(&buf, &sf->handle);
	else
		buf_add_path(&buf, path);

	buf_add_uint32(&buf, SSH_FILEXFER_ATTR_SIZE);
	buf_add_uint64(&buf, size);
	err = sftp_request(get_conn(sf, path),
			   sf == NULL ? SSH_FXP_SETSTAT : SSH_FXP_FSETSTAT,
			   &buf, SSH_FXP_STATUS, NULL);
	buf_free(&buf);

	return err;
}

static int sshfs_getattr(const char *path, struct stat *stbuf,
			 struct fuse_file_info *fi)
{
	int err;
	struct buffer buf;
	struct buffer outbuf;
	struct sshfs_file *sf = NULL;

	if (fi != NULL && !sshfs.fstat_workaround) {
		sf = get_sshfs_file(fi);
		if (!sshfs_file_is_conn(sf))
			return -EIO;
	}

	buf_init(&buf, 0);
	if(sf == NULL) {
		buf_add_path(&buf, path);
		err = sftp_request(get_conn(sf, path),
				   sshfs.follow_symlinks ? SSH_FXP_STAT : SSH_FXP_LSTAT,
				   &buf, SSH_FXP_ATTRS, &outbuf);
	}
	else {
		buf_add_buf(&buf, &sf->handle);
		err = sftp_request(sf->conn, SSH_FXP_FSTAT, &buf,
				   SSH_FXP_ATTRS, &outbuf);
	}
	if (!err) {
		err = buf_get_attrs(&outbuf, stbuf, NULL);
#ifdef __APPLE__
		stbuf->st_blksize = 0;
#endif
		buf_free(&outbuf);
	}
	buf_free(&buf);
	return err;
}

static int sshfs_truncate_zero(const char *path)
{
	int err;
	struct fuse_file_info fi;

	fi.flags = O_WRONLY | O_TRUNC;
	err = sshfs_open(path, &fi);
	if (!err)
		sshfs_release(path, &fi);

	return err;
}

static size_t calc_buf_size(off_t size, off_t offset)
{
	return offset + sshfs.max_read < size ? sshfs.max_read : size - offset;
}

static int sshfs_truncate_shrink(const char *path, off_t size)
{
	int res;
	char *data;
	off_t offset;
	struct fuse_file_info fi;

	data = calloc(size, 1);
	if (!data)
		return -ENOMEM;

	fi.flags = O_RDONLY;
	res = sshfs_open(path, &fi);
	if (res)
		goto out;

	for (offset = 0; offset < size; offset += res) {
		size_t bufsize = calc_buf_size(size, offset);
		res = sshfs_read(path, data + offset, bufsize, offset, &fi);
		if (res <= 0)
			break;
	}
	sshfs_release(path, &fi);
	if (res < 0)
		goto out;

	fi.flags = O_WRONLY | O_TRUNC;
	res = sshfs_open(path, &fi);
	if (res)
		goto out;

	for (offset = 0; offset < size; offset += res) {
		size_t bufsize = calc_buf_size(size, offset);
		res = sshfs_write(path, data + offset, bufsize, offset, &fi);
		if (res < 0)
			break;
	}
	if (res >= 0)
		res = sshfs_flush(path, &fi);
	sshfs_release(path, &fi);

out:
	free(data);
	return res;
}

static int sshfs_truncate_extend(const char *path, off_t size,
                                 struct fuse_file_info *fi)
{
	int res;
	char c = 0;
	struct fuse_file_info tmpfi;
	struct fuse_file_info *openfi = fi;
	if (!fi) {
		openfi = &tmpfi;
		openfi->flags = O_WRONLY;
		res = sshfs_open(path, openfi);
		if (res)
			return res;
	}
	res = sshfs_write(path, &c, 1, size - 1, openfi);
	if (res == 1)
		res = sshfs_flush(path, openfi);
	if (!fi)
		sshfs_release(path, openfi);

	return res;
}

/*
 * Work around broken sftp servers which don't handle
 * SSH_FILEXFER_ATTR_SIZE in SETSTAT request.
 *
 * If new size is zero, just open the file with O_TRUNC.
 *
 * If new size is smaller than current size, then copy file locally,
 * then open/trunc and send it back.
 *
 * If new size is greater than current size, then write a zero byte to
 * the new end of the file.
 */
static int sshfs_truncate_workaround(const char *path, off_t size,
                                     struct fuse_file_info *fi)
{
	if (size == 0)
		return sshfs_truncate_zero(path);
	else {
		struct stat stbuf;
		int err;
		err = sshfs_getattr(path, &stbuf, fi);
		if (err)
			return err;
		if (stbuf.st_size == size)
			return 0;
		else if (stbuf.st_size > size)
			return sshfs_truncate_shrink(path, size);
		else
			return sshfs_truncate_extend(path, size, fi);
	}
}

static int processing_init(void)
{
	int i;

	signal(SIGPIPE, SIG_IGN);

	pthread_mutex_init(&sshfs.lock, NULL);
	for (i = 0; i < sshfs.max_conns; i++)
		pthread_mutex_init(&sshfs.conns[i].lock_write, NULL);
	pthread_cond_init(&sshfs.outstanding_cond, NULL);
	sshfs.reqtab = g_hash_table_new(NULL, NULL);
	if (!sshfs.reqtab) {
		fprintf(stderr, "failed to create hash table\n");
		return -1;
	}
	if (sshfs.max_conns > 1) {
		sshfs.conntab = g_hash_table_new_full(g_str_hash, g_str_equal,
						      g_free, NULL);
		if (!sshfs.conntab) {
			fprintf(stderr, "failed to create hash table\n");
			return -1;
		}
	}
	return 0;
}

static struct fuse_operations sshfs_oper = {
		.init       = sshfs_init,
		.getattr    = sshfs_getattr,
		.access     = sshfs_access,
		.opendir    = sshfs_opendir,
		.readdir    = sshfs_readdir,
		.releasedir = sshfs_releasedir,
		.readlink   = sshfs_readlink,
		.mknod      = sshfs_mknod,
		.mkdir      = sshfs_mkdir,
		.symlink    = sshfs_symlink,
		.unlink     = sshfs_unlink,
		.rmdir      = sshfs_rmdir,
		.rename     = sshfs_rename,
		.link       = sshfs_link,
		.chmod      = sshfs_chmod,
		.chown      = sshfs_chown,
		.truncate   = sshfs_truncate,
		.utimens    = sshfs_utimens,
		.open       = sshfs_open,
		.flush      = sshfs_flush,
		.fsync      = sshfs_fsync,
		.release    = sshfs_release,
		.read       = sshfs_read,
		.write      = sshfs_write,
		.statfs     = sshfs_statfs,
		.create     = sshfs_create,
};

static void usage(const char *progname)
{
	printf(
"usage: %s [user@]host:[dir] mountpoint [options]\n"
"\n"
"    -h   --help            print help\n"
"    -V   --version         print version\n"
"    -f                     foreground operation\n"
"    -s                     disable multi-threaded operation\n"
"    -p PORT                equivalent to '-o port=PORT'\n"
"    -C                     equivalent to '-o compression=yes'\n"
"    -F ssh_configfile      specifies alternative ssh configuration file\n"
"    -1                     equivalent to '-o ssh_protocol=1'\n"
"    -o opt,[opt...]        mount options\n"
"    -o reconnect           reconnect to server\n"
"    -o delay_connect       delay connection to server\n"
"    -o sshfs_sync          synchronous writes\n"
"    -o no_readahead        synchronous reads (no speculative readahead)\n"
"    -o sync_readdir        synchronous readdir\n"
"    -d, --debug            print some debugging information (implies -f)\n"
"    -v, --verbose          print ssh replies and messages\n"
"    -o dir_cache=BOOL      enable caching of directory contents (names,\n"
"                           attributes, symlink targets) {yes,no} (default: yes)\n"
"    -o dcache_max_size=N   sets the maximum size of the directory cache (default: 10000)\n"
"    -o dcache_timeout=N    sets timeout for directory cache in seconds (default: 20)\n"
"    -o dcache_{stat,link,dir}_timeout=N\n"
"                           sets separate timeout for {attributes, symlinks, names}\n"
"    -o dcache_clean_interval=N\n"
"                           sets the interval for automatic cleaning of the\n"
"                           cache (default: 60)\n"
"    -o dcache_min_clean_interval=N\n"
"                           sets the interval for forced cleaning of the\n"
"                           cache if full (default: 5)\n"
"    -o direct_io           enable direct i/o\n"
"    -o workaround=LIST     colon separated list of workarounds\n"
"             none             no workarounds enabled\n"
"             [no]rename       fix renaming to existing file (default: off)\n"
"             [no]renamexdev   fix moving across filesystems (default: off)\n"
"             [no]truncate     fix truncate for old servers (default: off)\n"
"             [no]buflimit     fix buffer fillup bug in server (default: off)\n"
"             [no]fstat        always use stat() instead of fstat() (default: off)\n"
"             [no]createmode   always pass mode 0 to create (default: off)\n"
"    -o idmap=TYPE          user/group ID mapping (default: " IDMAP_DEFAULT ")\n"
"             none             no translation of the ID space\n"
"             user             only translate UID/GID of connecting user\n"
"             file             translate UIDs/GIDs contained in uidfile/gidfile\n"
"    -o uidfile=FILE        file containing username:remote_uid mappings\n"
"    -o gidfile=FILE        file containing groupname:remote_gid mappings\n"
"    -o nomap=TYPE          with idmap=file, how to handle missing mappings\n"
"             ignore           don't do any re-mapping\n"
"             error            return an error (default)\n"
"    -o ssh_command=CMD     execute CMD instead of 'ssh'\n"
"    -o ssh_protocol=N      ssh protocol to use (default: 2)\n"
"    -o sftp_server=SERV    path to sftp server or subsystem (default: sftp)\n"
"    -o directport=PORT     directly connect to PORT bypassing ssh\n"
"    -o passive             communicate over stdin and stdout bypassing network\n"
"    -o disable_hardlink    link(2) will return with errno set to ENOSYS\n"
"    -o transform_symlinks  transform absolute symlinks to relative\n"
"    -o follow_symlinks     follow symlinks on the server\n"
"    -o no_check_root       don't check for existence of 'dir' on server\n"
"    -o password_stdin      read password from stdin (only for pam_mount!)\n"
"    -o max_conns=N         open parallel SSH connections\n"
"    -o vsock=CID:PORT      connect to the given vsock\n"
"    -o SSHOPT=VAL          ssh options (see man ssh_config)\n"
"\n"
"FUSE Options:\n",
progname);
}

static int is_ssh_opt(const char *arg)
{
	if (arg[0] != '-') {
		unsigned arglen = strlen(arg);
		const char **o;
		for (o = ssh_opts; *o; o++) {
			unsigned olen = strlen(*o);
			if (arglen > olen && arg[olen] == '=' &&
			    strncasecmp(arg, *o, olen) == 0)
				return 1;
		}
	}
	return 0;
}

static int sshfs_opt_proc(void *data, const char *arg, int key,
                          struct fuse_args *outargs)
{
	(void) outargs; (void) data;
	char *tmp;

	switch (key) {
	case FUSE_OPT_KEY_OPT:
		if (is_ssh_opt(arg)) {
			tmp = g_strdup_printf("-o%s", arg);
			ssh_add_arg(tmp);
			g_free(tmp);
			return 0;
		}
		/* Pass through */
		return 1;

	case FUSE_OPT_KEY_NONOPT:
		if (!sshfs.host && strchr(arg, ':')) {
			sshfs.host = strdup(arg);
			return 0;
		}
		else if (!sshfs.mountpoint) {
#if defined(__CYGWIN__)
			/*
			 * On FUSE for Cygwin the mountpoint may be a drive or directory.
			 * Furthermore the mountpoint must NOT exist prior to mounting.
			 * So we cannot use realpath(3).
			 */
			if ((('A' <= arg[0] && arg[0] <= 'Z') || ('a' <= arg[0] && arg[0] <= 'z'))
				&& ':' == arg[1] && '\0' == arg[2]) {
				/* drive: make a copy */
				sshfs.mountpoint = strdup(arg);
			} else {
				/* path: split into dirname, basename and check dirname */
				char *dir;
				const char *base;
				const char *slash = strrchr(arg, '/');
				if (slash) {
					char *tmp = strndup(arg, slash == arg ? 1 : slash - arg);
					dir = tmp ? realpath(tmp, NULL) : 0;
					base = slash + 1;
					free(tmp);
				} else {
					dir = realpath(".", NULL);
					base = arg;
				}
				if (dir) {
					slash = '/' == dir[0] && '\0' == dir[1] ? "" : "/";
					asprintf(&sshfs.mountpoint, "%s%s%s", dir, slash, base);
					free(dir);
				}
			}
#else
			int fd, len;
			if (sscanf(arg, "/dev/fd/%u%n", &fd, &len) == 1 &&
			    len == strlen(arg)) {
				/*
				 * Allow /dev/fd/N unchanged; it can be
				 * use for pre-mounting a generic fuse
				 * mountpoint to later be completely
				 * unprivileged with libfuse >= 3.3.0.
				 */
				sshfs.mountpoint = strdup(arg);
			} else {
				sshfs.mountpoint = realpath(arg, NULL);
#ifdef __APPLE__
				if (!sshfs.mountpoint) {
					/*
					 * The mountpoint does not exist, yet.
					 * macFUSE will try to create it before
					 * mounting the volume.
					 */
					sshfs.mountpoint = strdup(arg);
				}
#endif
			}
#endif
			if (!sshfs.mountpoint) {
				fprintf(stderr, "sshfs: bad mount point `%s': %s\n",
					arg, strerror(errno));
				return -1;
			}
			return 0;
		}
		fprintf(stderr, "sshfs: invalid argument `%s'\n", arg);
		return -1;


	case KEY_PORT:
		tmp = g_strdup_printf("-oPort=%s", arg + 2);
		ssh_add_arg(tmp);
		g_free(tmp);
		return 0;

	case KEY_COMPRESS:
		ssh_add_arg("-oCompression=yes");
		return 0;

	case KEY_CONFIGFILE:
		tmp = g_strdup_printf("-F%s", arg + 2);
		ssh_add_arg(tmp);
		g_free(tmp);
		return 0;

	default:
		fprintf(stderr, "internal error\n");
		abort();
	}
}

static int workaround_opt_proc(void *data, const char *arg, int key,
			       struct fuse_args *outargs)
{
	(void) data; (void) key; (void) outargs;
	fprintf(stderr, "unknown workaround: '%s'\n", arg);
	return -1;
}

static int parse_workarounds(void)
{
	int res;
	/* Need separate variables because literals are const
	   char */
	char argv0[] = "";
	char argv1[] = "-o";
	char *argv[] = { argv0, argv1, sshfs.workarounds, NULL };
	struct fuse_args args = FUSE_ARGS_INIT(3, argv);
	char *s = sshfs.workarounds;
	if (!s)
		return 0;

	while ((s = strchr(s, ':')))
		*s = ',';

	res = fuse_opt_parse(&args, &sshfs, workaround_opts,
			     workaround_opt_proc);
	fuse_opt_free_args(&args);

	return res;
}

static int read_password(void)
{
	int size = getpagesize();
	int max_password = MIN(MAX_PASSWORD, size - 1);
	int n;

	sshfs.password = mmap(NULL, size, PROT_READ | PROT_WRITE,
			      MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED,
			      -1, 0);
	if (sshfs.password == MAP_FAILED) {
		perror("Failed to allocate locked page for password");
		return -1;
	}
	if (mlock(sshfs.password, size) != 0) {
		memset(sshfs.password, 0, size);
		munmap(sshfs.password, size);
		sshfs.password = NULL;
		perror("Failed to allocate locked page for password");
		return -1;
	}

	/* Don't use fgets() because password might stay in memory */
	for (n = 0; n < max_password; n++) {
		int res;

		res = read(0, &sshfs.password[n], 1);
		if (res == -1) {
			perror("Reading password");
			return -1;
		}
		if (res == 0) {
			sshfs.password[n] = '\n';
			break;
		}
		if (sshfs.password[n] == '\n')
			break;
	}
	if (n == max_password) {
		fprintf(stderr, "Password too long\n");
		return -1;
	}
	sshfs.password[n+1] = '\0';
	ssh_add_arg("-oNumberOfPasswordPrompts=1");

	return 0;
}

// Behaves similarly to strtok(), but allows for the ' ' delimiter to be escaped
// by '\ '.
static char *tokenize_on_space(char *str)
{
	static char *pos = NULL;
	char *start = NULL;

	if (str)
		pos = str;

	if (!pos)
		return NULL;

	// trim any leading spaces
	while (*pos == ' ')
		pos++;

	start = pos;

	while (pos && *pos != '\0') {
		// break on space, but not on '\ '
		if (*pos == ' ' && *(pos - 1) != '\\') {
			break;
		}
		pos++;
	}

	if (*pos == '\0') {
		pos = NULL;
	}
	else {
		*pos = '\0';
		pos++;
	}

	return start;
}

static void set_ssh_command(void)
{
	char *token = NULL;
	int i = 0;

	token = tokenize_on_space(sshfs.ssh_command);
	while (token != NULL) {
		if (i == 0) {
			replace_arg(&sshfs.ssh_args.argv[0], token);
		} else {
			if (fuse_opt_insert_arg(&sshfs.ssh_args, i, token) == -1)
				_exit(1);
		}
		i++;

		token = tokenize_on_space(NULL);
	}
}

static char *find_base_path(void)
{
	char *s = sshfs.host;
	char *d = s;

	for (; *s && *s != ':'; s++) {
		if (*s == '[') {
			/*
			 * Handle IPv6 numerical address enclosed in square
			 * brackets
			 */
			s++;
			for (; *s != ']'; s++) {
				if (!*s) {
					fprintf(stderr,	"missing ']' in hostname\n");
					exit(1);
				}
				*d++ = *s;
			}
		} else {
			*d++ = *s;
		}

	}
	*d++ = '\0';
	s++;

	return s;
}

static char *fsname_escape_commas(char *fsnameold)
{
	char *fsname = g_malloc(strlen(fsnameold) * 2 + 1);
	char *d = fsname;
	char *s;

	for (s = fsnameold; *s; s++) {
		if (*s == '\\' || *s == ',')
			*d++ = '\\';
		*d++ = *s;
	}
	*d = '\0';
	g_free(fsnameold);

	return fsname;
}

static int ssh_connect(void)
{
	int res;

	res = processing_init();
	if (res == -1)
		return -1;

	if (!sshfs.delay_connect) {
		if (connect_remote(&sshfs.conns[0]) == -1)
			return -1;

		if (!sshfs.no_check_root &&
		    sftp_check_root(&sshfs.conns[0], sshfs.base_path) != 0)
			return -1;

	}
	return 0;
}

/* number of ':' separated fields in a passwd/group file that we care
 * about */
#define IDMAP_FIELDS 3

/* given a line from a uidmap or gidmap, parse out the name and id */
static void parse_idmap_line(char *line, const char* filename,
		const unsigned int lineno, uint32_t *ret_id, char **ret_name,
		const int eof)
{
	/* chomp off the trailing newline */
	char *p = line;
	if ((p = strrchr(line, '\n')))
		*p = '\0';
	else if (!eof) {
		fprintf(stderr, "%s:%u: line too long\n", filename, lineno);
		exit(1);
	}
	char *tokens[IDMAP_FIELDS];
	char *tok;
	int i;
	for (i = 0; (tok = strsep(&line, ":")) && (i < IDMAP_FIELDS) ; i++) {
		tokens[i] = tok;
	}

	char *name_tok, *id_tok;
	if (i == 2) {
		/* assume name:id format */
		name_tok = tokens[0];
		id_tok = tokens[1];
	} else if (i >= IDMAP_FIELDS) {
		/* assume passwd/group file format */
		name_tok = tokens[0];
		id_tok = tokens[2];
	} else {
		fprintf(stderr, "%s:%u: unknown format\n", filename, lineno);
		exit(1);
	}

	errno = 0;
	uint32_t remote_id = strtoul(id_tok, NULL, 10);
	if (errno) {
		fprintf(stderr, "Invalid id number on line %u of '%s': %s\n",
				lineno, filename, strerror(errno));
		exit(1);
	}

	*ret_name = strdup(name_tok);
	*ret_id = remote_id;
}

/* read a uidmap or gidmap */
static void read_id_map(char *file, uint32_t *(*map_fn)(char *),
		const char *name_id, GHashTable **idmap, GHashTable **r_idmap)
{
	*idmap = g_hash_table_new(NULL, NULL);
	*r_idmap = g_hash_table_new(NULL, NULL);
	FILE *fp;
	char line[LINE_MAX];
	unsigned int lineno = 0;
	uid_t local_uid = getuid();

	fp = fopen(file, "r");
	if (fp == NULL) {
		fprintf(stderr, "failed to open '%s': %s\n",
				file, strerror(errno));
		exit(1);
	}
	struct stat st;
	if (fstat(fileno(fp), &st) == -1) {
		fprintf(stderr, "failed to stat '%s': %s\n", file,
				strerror(errno));
		exit(1);
	}
	if (st.st_uid != local_uid) {
		fprintf(stderr, "'%s' is not owned by uid %lu\n", file,
				(unsigned long)local_uid);
		exit(1);
	}
	if (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) {
		fprintf(stderr, "'%s' is writable by other users\n", file);
		exit(1);
	}

	while (fgets(line, LINE_MAX, fp) != NULL) {
		lineno++;
		uint32_t remote_id;
		char *name;

		/* skip blank lines */
		if (line[0] == '\n' || line[0] == '\0')
			continue;

		parse_idmap_line(line, file, lineno, &remote_id, &name, feof(fp));

		uint32_t *local_id = map_fn(name);
		if (local_id == NULL) {
			/* not found */
			DEBUG("%s(%u): no local %s\n", name, remote_id, name_id);
			free(name);
			continue;
		}

		DEBUG("%s: remote %s %u => local %s %u\n",
				name, name_id, remote_id, name_id, *local_id);
		g_hash_table_insert(*idmap, GUINT_TO_POINTER(remote_id), GUINT_TO_POINTER(*local_id));
		g_hash_table_insert(*r_idmap, GUINT_TO_POINTER(*local_id), GUINT_TO_POINTER(remote_id));
		free(name);
		free(local_id);
	}

	if (fclose(fp) == EOF) {
		fprintf(stderr, "failed to close '%s': %s",
				file, strerror(errno));
		exit(1);
	}
}

/* given a username, return a pointer to its uid, or NULL if it doesn't
 * exist on this system */
static uint32_t *username_to_uid(char *name)
{
	errno = 0;
	struct passwd *pw = getpwnam(name);
	if (pw == NULL) {
		if (errno == 0) {
			/* "does not exist" */
			return NULL;
		}
		fprintf(stderr, "Failed to look up user '%s': %s\n",
				name, strerror(errno));
		exit(1);
	}
	uint32_t *r = malloc(sizeof(uint32_t));
	if (r == NULL) {
		fprintf(stderr, "sshfs: memory allocation failed\n");
		abort();
	}
	*r = pw->pw_uid;
	return r;
}

/* given a groupname, return a pointer to its gid, or NULL if it doesn't
 * exist on this system */
static uint32_t *groupname_to_gid(char *name)
{
	errno = 0;
	struct group *gr = getgrnam(name);
	if (gr == NULL) {
		if (errno == 0) {
			/* "does not exist" */
			return NULL;
		}
		fprintf(stderr, "Failed to look up group '%s': %s\n",
				name, strerror(errno));
		exit(1);
	}
	uint32_t *r = malloc(sizeof(uint32_t));
	if (r == NULL) {
		fprintf(stderr, "sshfs: memory allocation failed\n");
		abort();
	}
	*r = gr->gr_gid;
	return r;
}

static inline void load_uid_map(void)
{
	read_id_map(sshfs.uid_file, &username_to_uid, "uid", &sshfs.uid_map, &sshfs.r_uid_map);
}

static inline void load_gid_map(void)
{
	read_id_map(sshfs.gid_file, &groupname_to_gid, "gid", &sshfs.gid_map, &sshfs.r_gid_map);
}

#ifdef __APPLE__
int main(int argc, char *argv[], __unused char *envp[], char **exec_path)
#else
int main(int argc, char *argv[])
#endif
{
	int res;
	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
	char *tmp;
	char *fsname;
	const char *sftp_server;
	struct fuse *fuse;
	struct fuse_session *se;
	int i;

#ifdef __APPLE__
	if (!realpath(*exec_path, sshfs_program_path)) {
		memset(sshfs_program_path, 0, PATH_MAX);
	}
#endif /* __APPLE__ */

#ifdef __APPLE__
	sshfs.blksize = 0;
#else
	sshfs.blksize = 4096;
#endif
	/* SFTP spec says all servers should allow at least 32k I/O */
	sshfs.max_read = 32768;
	sshfs.max_write = 32768;
#ifdef __APPLE__
	sshfs.rename_workaround = 1;
#else
	sshfs.rename_workaround = 0;
#endif
	sshfs.renamexdev_workaround = 0;
	sshfs.truncate_workaround = 0;
	sshfs.buflimit_workaround = 0;
	sshfs.createmode_workaround = 0;
	sshfs.ssh_ver = 2;
	sshfs.progname = argv[0];
	sshfs.max_conns = 1;
	sshfs.ptyfd = -1;
	sshfs.dir_cache = 1;
	sshfs.show_help = 0;
	sshfs.show_version = 0;
	sshfs.singlethread = 0;
	sshfs.foreground = 0;
	sshfs.ptypassivefd = -1;
	sshfs.delay_connect = 0;
	sshfs.passive = 0;
	sshfs.detect_uid = 0;
	if (strcmp(IDMAP_DEFAULT, "none") == 0) {
		sshfs.idmap = IDMAP_NONE;
	} else if (strcmp(IDMAP_DEFAULT, "user") == 0) {
		sshfs.idmap = IDMAP_USER;
	} else {
		fprintf(stderr, "bad idmap default value built into sshfs; "
		    "assuming none (bad logic in configure script?)\n");
		sshfs.idmap = IDMAP_NONE;
	}
	sshfs.nomap = NOMAP_ERROR;
	ssh_add_arg("ssh");
	ssh_add_arg("-x");
	ssh_add_arg("-a");
	ssh_add_arg("-oClearAllForwardings=yes");

	if (fuse_opt_parse(&args, &sshfs, sshfs_opts, sshfs_opt_proc) == -1 ||
	    parse_workarounds() == -1)
		exit(1);

	if (sshfs.show_version) {
		printf("SSHFS version %s\n", PACKAGE_VERSION);
		printf("FUSE library version %s\n", fuse_pkgversion());
#if !defined(__CYGWIN__)
		fuse_lowlevel_version();
#endif
		exit(0);
	}

	if (sshfs.show_help) {
		usage(args.argv[0]);
		fuse_lib_help(&args);
		exit(0);
	} else if (!sshfs.host) {
		fprintf(stderr, "missing host\n");
		fprintf(stderr, "see `%s -h' for usage\n", argv[0]);
		exit(1);
	} else if (!sshfs.mountpoint) {
		fprintf(stderr, "error: no mountpoint specified\n");
		fprintf(stderr, "see `%s -h' for usage\n", argv[0]);
		exit(1);
	}

	if (sshfs.idmap == IDMAP_USER)
		sshfs.detect_uid = 1;
	else if (sshfs.idmap == IDMAP_FILE) {
		sshfs.uid_map = NULL;
		sshfs.gid_map = NULL;
		sshfs.r_uid_map = NULL;
		sshfs.r_gid_map = NULL;
		if (!sshfs.uid_file && !sshfs.gid_file) {
			fprintf(stderr, "need a uidfile or gidfile with idmap=file\n");
			exit(1);
		}
		if (sshfs.uid_file)
			load_uid_map();
		if (sshfs.gid_file)
			load_gid_map();
	}
	free(sshfs.uid_file);
	free(sshfs.gid_file);

	DEBUG("SSHFS version %s\n", PACKAGE_VERSION);

	/* Force sshfs to the foreground when using stdin+stdout */
	if (sshfs.passive)
		sshfs.foreground = 1;


	if (sshfs.passive && sshfs.password_stdin) {
		fprintf(stderr, "the password_stdin and passive options cannot both be specified\n");
		exit(1);
	}

	if (sshfs.password_stdin) {
		res = read_password();
		if (res == -1)
			exit(1);
	}

	if (sshfs.debug)
		sshfs.foreground = 1;

	if (sshfs.buflimit_workaround)
		/* Work around buggy sftp-server in OpenSSH.  Without this on
		   a slow server a 10Mbyte buffer would fill up and the server
		   would abort */
		sshfs.max_outstanding_len = 8388608;
	else
		sshfs.max_outstanding_len = ~0;

	if (sshfs.max_conns > 1) {
		if (sshfs.buflimit_workaround) {
			fprintf(stderr, "buflimit workaround is not supported with parallel connections\n");
			exit(1);
		}

		if (sshfs.password_stdin) {
			fprintf(stderr, "password_stdin option cannot be specified with parallel connections\n");
			exit(1);
		}

		if (sshfs.passive) {
			fprintf(stderr, "passive option cannot be specified with parallel connections\n");
			exit(1);
		}
	} else if (sshfs.max_conns <= 0) {
		fprintf(stderr, "value of max_conns option must be at least 1\n");
		exit(1);
	}

	sshfs.conns = g_new0(struct conn, sshfs.max_conns);
	for (i = 0; i < sshfs.max_conns; i++) {
		sshfs.conns[i].rfd = -1;
		sshfs.conns[i].wfd = -1;
	}

	fsname = g_strdup(sshfs.host);
	sshfs.base_path = g_strdup(find_base_path());

	if (sshfs.ssh_command)
		set_ssh_command();

	tmp = g_strdup_printf("-%i", sshfs.ssh_ver);
	ssh_add_arg(tmp);
	g_free(tmp);
	ssh_add_arg(sshfs.host);
	if (sshfs.sftp_server)
		sftp_server = sshfs.sftp_server;
	else if (sshfs.ssh_ver == 1)
		sftp_server = SFTP_SERVER_PATH;
	else
		sftp_server = "sftp";

	if (sshfs.ssh_ver != 1 && strchr(sftp_server, '/') == NULL)
		ssh_add_arg("-s");

	ssh_add_arg(sftp_server);
	free(sshfs.sftp_server);

	res = cache_parse_options(&args);
	if (res == -1)
		exit(1);

	sshfs.randseed = time(0);

	if (sshfs.max_read > 65536)
		sshfs.max_read = 65536;
	if (sshfs.max_write > 65536)
		sshfs.max_write = 65536;

	fsname = fsname_escape_commas(fsname);
	tmp = g_strdup_printf("-osubtype=sshfs,fsname=%s", fsname);
	fuse_opt_insert_arg(&args, 1, tmp);
	g_free(tmp);
	g_free(fsname);

	if(sshfs.dir_cache)
		sshfs.op = cache_wrap(&sshfs_oper);
	else
		sshfs.op = &sshfs_oper;
	fuse = fuse_new(&args, sshfs.op,
			sizeof(struct fuse_operations), NULL);
	if(fuse == NULL)
		exit(1);
	se = fuse_get_session(fuse);
	res = fuse_set_signal_handlers(se);
	if (res != 0) {
		fuse_destroy(fuse);
		exit(1);
	}

	res = fuse_mount(fuse, sshfs.mountpoint);
	if (res != 0) {
		fuse_destroy(fuse);
		exit(1);
	}

#if !defined(__CYGWIN__)
	res = fcntl(fuse_session_fd(se), F_SETFD, FD_CLOEXEC);
	if (res == -1)
		perror("WARNING: failed to set FD_CLOEXEC on fuse device");
#endif

	/*
	 * FIXME: trim $PATH so it doesn't contain anything inside the
	 * mountpoint, which would deadlock.
	 */
	res = ssh_connect();
	if (res == -1) {
		fuse_unmount(fuse);
		fuse_destroy(fuse);
		exit(1);
	}

	res = fuse_daemonize(sshfs.foreground);
	if (res == -1) {
		fuse_unmount(fuse);
		fuse_destroy(fuse);
		exit(1);
	}

	if (sshfs.singlethread)
		res = fuse_loop(fuse);
	else
		res = fuse_loop_mt(fuse, 0);

	if (res != 0)
		res = 1;
	else
		res = 0;

	fuse_remove_signal_handlers(se);
	fuse_unmount(fuse);
	fuse_destroy(fuse);

	if (sshfs.debug) {
		unsigned int avg_rtt = 0;

		if (sshfs.num_sent)
			avg_rtt = sshfs.total_rtt / sshfs.num_sent;

		DEBUG("\n"
		      "sent:               %llu messages, %llu bytes\n"
		      "received:           %llu messages, %llu bytes\n"
		      "rtt min/max/avg:    %ums/%ums/%ums\n"
		      "num connect:        %u\n",
		      (unsigned long long) sshfs.num_sent,
		      (unsigned long long) sshfs.bytes_sent,
		      (unsigned long long) sshfs.num_received,
		      (unsigned long long) sshfs.bytes_received,
		      sshfs.min_rtt, sshfs.max_rtt, avg_rtt,
		      sshfs.num_connect);
	}

	fuse_opt_free_args(&args);
	fuse_opt_free_args(&sshfs.ssh_args);
	free(sshfs.directport);

	return res;
}


================================================
FILE: sshfs.rst
================================================
=======
 SSHFS
=======

---------------------------------------------
 filesystem client based on SSH
---------------------------------------------

:Manual section: 1
:Manual group: User Commands

Synopsis
========

To mount a filesystem::

   sshfs [user@]host:[dir] mountpoint [options]

If *host* is a numeric IPv6 address, it needs to be enclosed in square
brackets.

To unmount it::

  fusermount3 -u mountpoint   # Linux
  umount mountpoint           # OS X, FreeBSD

Description
===========

SSHFS allows you to mount a remote filesystem using SSH (more precisely, the SFTP
subsystem). Most SSH servers support and enable this SFTP access by default, so SSHFS is
very simple to use - there's nothing to do on the server-side.

By default, file permissions are ignored by SSHFS. Any user that can access the filesystem
will be able to perform any operation that the remote server permits - based on the
credentials that were used to connect to the server. If this is undesired, local
permission checking can be enabled with ``-o default_permissions``.

By default, only the mounting user will be able to access the filesystem. Access for other
users can be enabled by passing ``-o allow_other``. In this case you most likely also
want to use ``-o default_permissions``.

It is recommended to run SSHFS as regular user (not as root).  For this to work the
mountpoint must be owned by the user.  If username is omitted SSHFS will use the local
username. If the directory is omitted, SSHFS will mount the (remote) home directory.  If
you need to enter a password sshfs will ask for it (actually it just runs ssh which ask
for the password if needed).


Options
=======


-o opt,[opt...]
   mount options, see below for details. A a variety of SSH options can
   be given here as well, see the manual pages for *sftp(1)* and
   *ssh_config(5)*.

-h, --help
   print help and exit.

-V, --version
   print version information and exit.

-d, --debug
   print debugging information.

-p PORT
   equivalent to '-o port=PORT'

-f
   do not daemonize, stay in foreground.

-s
   Single threaded operation.

-C
   equivalent to '-o compression=yes'

-F ssh_configfile
   specifies alternative ssh configuration file

-1
   equivalent to '-o ssh_protocol=1'

-o reconnect
   automatically reconnect to server if connection is
   interrupted. Attempts to access files that were opened before the
   reconnection will give errors and need to be re-opened.

-o delay_connect
   Don't immediately connect to server, wait until mountpoint is first
   accessed.

-o sshfs_sync
   synchronous writes. This will slow things down, but may be useful
   in some situations.

-o no_readahead
   Only read exactly the data that was requested, instead of
   speculatively reading more to anticipate the next read request.

-o sync_readdir
   synchronous readdir. This will slow things down, but may be useful
   in some situations.

-o workaround=LIST
   Enable the specified workaround. See the `Caveats` section below
   for some additional information. Possible values are:

   :rename: Emulate overwriting an existing file by deleting and
        renaming.
   :renamexdev: Make rename fail with EXDEV instead of the default EPERM
        to allow moving files across remote filesystems.
   :truncate: Work around servers that don't support truncate by
        coping the whole file, truncating it locally, and sending it
        back.
   :fstat: Work around broken servers that don't support *fstat()* by
           using *stat* instead.
   :buflimit: Work around OpenSSH "buffer fillup" bug.
   :createmode: Work around broken servers that produce an error when passing a
                non-zero mode to create, by always passing a mode of 0.

-o idmap=TYPE
   How to map remote UID/GIDs to local values. Possible values are:

   :none: no translation of the ID space (default).

   :user: map the UID/GID of the remote user to UID/GID of the
            mounting user.

   :file: translate UIDs/GIDs based upon the contents of `--uidfile`
            and `--gidfile`.

-o uidfile=FILE
   file containing ``username:uid`` mappings for `-o idmap=file`

-o gidfile=FILE
   file containing ``groupname:gid`` mappings for `-o idmap=file`

-o nomap=TYPE
   with idmap=file, how to handle missing mappings:

   :ignore: don't do any re-mapping
   :error:  return an error (default)

-o ssh_command=CMD
   execute CMD instead of 'ssh'

-o ssh_protocol=N
   ssh protocol to use (default: 2)

-o sftp_server=SERV
   path to sftp server or subsystem (default: sftp)

-o directport=PORT
   directly connect to PORT bypassing ssh

-o vsock=CID:PORT
   directly connect using a vsock to CID:PORT bypassing ssh

-o passive
   communicate over stdin and stdout bypassing network. Useful for
   mounting local filesystem on the remote side.  An example using
   dpipe command would be ``dpipe /usr/lib/openssh/sftp-server = ssh
   RemoteHostname sshfs :/directory/to/be/shared ~/mnt/src -o passive``

-o disable_hardlink
   With this option set, attempts to call `link(2)` will fail with
   error code ENOSYS.

-o transform_symlinks
   transform absolute symlinks on remote side to relative
   symlinks. This means that if e.g. on the server side
   ``/foo/bar/com`` is a symlink to ``/foo/blub``, SSHFS will
   transform the link target to ``../blub`` on the client side.

-o follow_symlinks
   follow symlinks on the server, i.e. present them as regular
   files on the client. If a symlink is dangling (i.e, the target does
   not exist) the behavior depends on the remote server - the entry
   may appear as a symlink on the client, or it may appear as a
   regular file that cannot be accessed.

-o no_check_root
   don't check for existence of 'dir' on server

-o password_stdin
   read password from stdin (only for pam_mount!)

-o dir_cache=BOOL
   Enables (*yes*) or disables (*no*) the SSHFS directory cache.  The
   directory cache holds the names of directory entries. Enabling it
   allows `readdir(3)` system calls to be processed without network
   access.

-o dcache_max_size=N
   sets the maximum size of the directory cache.

-o dcache_timeout=N
   sets timeout for directory cache in seconds.

-o dcache_{stat,link,dir}_timeout=N
   sets separate timeout for {attributes, symlinks, names} in  the
   directory cache.

-o dcache_clean_interval=N
   sets the interval for automatic cleaning of the directory cache.

-o dcache_min_clean_interval=N
   sets the interval for forced cleaning of the directory cache
   when full.

-o direct_io
   This option disables the use of page cache (file content cache) in
   the kernel for this filesystem.
   This has several affects:

   1. Each read() or write() system call will initiate one or more read or
      write operations, data will not be cached in the kernel.

   2. The return value of the read() and write() system calls will correspond
      to the return values of the read and write operations. This is useful
      for example if the file size is not known in advance (before reading it).
      e.g. /proc filesystem

-o max_conns=N
   sets the maximum number of simultaneous SSH connections
   to use. Each connection is established with a separate SSH process.
   The primary purpose of this feature is to improve the responsiveness of the
   file system during large file transfers. When using more than once
   connection, the *password_stdin* and *passive* options can not be
   used, and the *buflimit* workaround is not supported.

In addition, SSHFS accepts several options common to all FUSE file
systems. These are described in the `mount.fuse` manpage (look
for "general", "libfuse specific", and "high-level API" options).

Caveats / Workarounds
=====================

Hardlinks
~~~~~~~~~

If the SSH server supports the *hardlinks* extension, SSHFS will allow
you to create hardlinks. However, hardlinks will always appear as
individual files when seen through an SSHFS mount, i.e. they will
appear to have different inodes and an *st_nlink* value of 1.


Rename
~~~~~~

Some SSH servers do not support atomically overwriting the destination
when renaming a file. In this case you will get an error when you
attempt to rename a file and the destination already exists. A
workaround is to first remove the destination file, and then do the
rename. SSHFS can do this automatically if you call it with `-o
workaround=rename`. However, in this case it is still possible that
someone (or something) recreates the destination file after SSHFS has
removed it, but before SSHFS had the time to rename the old file. In
this case, the rename will still fail.


Permission denied when moving files across remote filesystems
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Most SFTP servers return only a generic "failure" when failing to rename
across filesystem boundaries (EXDEV).  sshfs normally converts this generic
failure to a permission denied error (EPERM).  If the option ``-o
workaround=renamexdev`` is given, generic failures will be considered EXDEV
errors which will make programs like `mv(1)` attempt to actually move the
file after the failed rename.


SSHFS hangs for no apparent reason
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In some cases, attempts to access the SSHFS mountpoint may freeze if
no filesystem activity has occurred for some time. This is typically
caused by the SSH connection being dropped because of inactivity
without SSHFS being informed about that. As a workaround, you can try
to mount with ``-o ServerAliveInterval=15``. This will force the SSH
connection to stay alive even if you have no activity.


SSHFS hangs after the connection was interrupted
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

By default, network operations in SSHFS run without timeouts, mirroring the
default behavior of SSH itself. As a consequence, if the connection to the
remote host is interrupted (e.g. because a network cable was removed),
operations on files or directories under the mountpoint will block until the
connection is either restored or closed altogether (e.g. manually).
Applications that try to access such files or directories will generally appear
to "freeze" when this happens.

If it is acceptable to discard data being read or written, a quick workaround
is to kill the responsible ``sshfs`` process, which will make any blocking
operations on the mounted filesystem error out and thereby "unfreeze" the
relevant applications. Note that force unmounting with ``fusermount -zu``, on
the other hand, does not help in this case and will leave read/write operations
in the blocking state.

For a more automatic solution, one can use the ``-o ServerAliveInterval=15``
option mentioned above, which will drop the connection after not receiving a
response for 3 * 15 = 45 seconds from the remote host. By also supplying ``-o
reconnect``, one can ensure that the connection is re-established as soon as
possible afterwards. As before, this will naturally lead to loss of data that
was in the process of being read or written at the time when the connection was
interrupted.


Mounting from /etc/fstab
========================

To mount an SSHFS filesystem from ``/etc/fstab``, simply use ``sshfs``
as the file system type. (For backwards compatibility, you may also
use ``fuse.sshfs``).


See also
========

The `mount.fuse(8)` manpage.

Getting Help
============

If you need help, please ask on the <fuse-sshfs@lists.sourceforge.net>
mailing list (subscribe at
https://lists.sourceforge.net/lists/listinfo/fuse-sshfs).

Please report any bugs on the GitHub issue tracker at
https://github.com/libfuse/libfuse/issues.


Authors
=======

SSHFS is currently maintained by Nikolaus Rath <Nikolaus@rath.org>,
and was created by Miklos Szeredi <miklos@szeredi.hu>.

This man page was originally written by Bartosz Fenski
<fenio@debian.org> for the Debian GNU/Linux distribution (but it may
be used by others).


================================================
FILE: test/.gitignore
================================================
__pycache__/


================================================
FILE: test/appveyor-build.sh
================================================
#!/bin/bash
set -e

machine=$(uname -m)
mkdir "build-$machine"
cd "build-$machine"
meson ..
ninja


================================================
FILE: test/conftest.py
================================================
import sys
import pytest
import time
import re

# If a test fails, wait a moment before retrieving the captured stdout/stderr.
# When using a server process, this makes sure that we capture any potential
# output of the server that comes *after* a test has failed. For example, if a
# request handler raises an exception, the server first signals an error to
# FUSE (causing the test to fail), and then logs the exception. Without the
# extra delay, the exception will go into nowhere.


@pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem):
    outcome = yield
    failed = outcome.excinfo is not None
    if failed:
        time.sleep(1)


@pytest.fixture()
def pass_capfd(request, capfd):
    """Provide capfd object to UnitTest instances"""
    request.instance.capfd = capfd


def check_test_output(capfd):
    (stdout, stderr) = capfd.readouterr()

    # Write back what we've read (so that it will still be printed.
    sys.stdout.write(stdout)
    sys.stderr.write(stderr)

    # Strip out false positives
    for (pattern, flags, count) in capfd.false_positives:
        cp = re.compile(pattern, flags)
        (stdout, cnt) = cp.subn("", stdout, count=count)
        if count == 0 or count - cnt > 0:
            stderr = cp.sub("", stderr, count=count - cnt)

    patterns = [
        r"\b{}\b".format(x)
        for x in (
            "exception",
            "error",
            "warning",
            "fatal",
            "traceback",
            "fault",
            "crash(?:ed)?",
            "abort(?:ed)",
            "uninitiali[zs]ed",
        )
    ]
    patterns += ["^==[0-9]+== "]
    for pattern in patterns:
        cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE)
        hit = cp.search(stderr)
        if hit:
            raise AssertionError(
                'Suspicious output to stderr (matched "%s")' % hit.group(0)
            )
        hit = cp.search(stdout)
        if hit:
            raise AssertionError(
                'Suspicious output to stdout (matched "%s")' % hit.group(0)
            )


def register_output(self, pattern, count=1, flags=re.MULTILINE):
    """Register *pattern* as false positive for output checking

    This prevents the test from failing because the output otherwise
    appears suspicious.
    """

    self.false_positives.append((pattern, flags, count))


# This is a terrible hack that allows us to access the fixtures from the
# pytest_runtest_call hook. Among a lot of other hidden assumptions, it probably
# relies on tests running sequential (i.e., don't dare to use e.g. the xdist
# plugin)
current_capfd = None


@pytest.fixture(autouse=True)
def save_cap_fixtures(request, capfd):
    global current_capfd
    capfd.false_positives = []

    # Monkeypatch in a function to register false positives
    type(capfd).register_output = register_output

    if request.config.getoption("capture") == "no":
        capfd = None
    current_capfd = capfd
    bak = current_capfd
    yield

    # Try to catch problems with this hack (e.g. when running tests
    # simultaneously)
    assert bak is current_capfd
    current_capfd = None


@pytest.hookimpl(trylast=True)
def pytest_runtest_call(item):
    capfd = current_capfd
    if capfd is not None:
        check_test_output(capfd)


================================================
FILE: test/lint.sh
================================================
#!/bin/bash
set -e
pip3 install --user pre-commit
pre-commit run --all-files --show-diff-on-failure


================================================
FILE: test/lsan_suppress.txt
================================================
# Suppression file for address sanitizer.

# There are some leaks in command line option parsing. They should be
# fixed at some point, but are harmless since the consume just a small,
# constant amount of memory and do not grow.
leak:fuse_opt_parse


# Leaks in fusermount3 are harmless as well (it's a short-lived
# process) - but patches are welcome!
leak:fusermount.c


================================================
FILE: test/meson.build
================================================
test_scripts = [ 'conftest.py', 'pytest.ini', 'test_sshfs.py',
                 'util.py' ]
custom_target('test_scripts', input: test_scripts,
              output: test_scripts, build_by_default: true,
              command: ['cp', '-fPp',
                        '@INPUT@', meson.current_build_dir() ])

# Provide something helpful when running 'ninja test'
wrong_cmd = executable('wrong_command', 'wrong_command.c',
                       install: false)
test('wrong_cmd', wrong_cmd)


================================================
FILE: test/pytest.ini
================================================
[pytest]
addopts = --verbose --assert=rewrite --tb=native -x -r a

markers = uses_fuse: Mark to indicate that FUSE is available on the system running the test


================================================
FILE: test/test_sshfs.py
================================================
#!/usr/bin/env python3

if __name__ == "__main__":
    import pytest
    import sys

    sys.exit(pytest.main([__file__] + sys.argv[1:]))

import subprocess
import os
import sys
import pytest
import stat
import shutil
import filecmp
import errno
from tempfile import NamedTemporaryFile
from util import (
    wait_for_mount,
    umount,
    cleanup,
    base_cmdline,
    basename,
    fuse_test_marker,
    safe_sleep,
    os_create,
    os_open,
)
from os.path import join as pjoin

TEST_FILE = __file__

pytestmark = fuse_test_marker()

with open(TEST_FILE, "rb") as fh:
    TEST_DATA = fh.read()


def name_generator(__ctr=[0]) -> str:
    """Generate a fresh filename on each call"""

    __ctr[0] += 1
    return f"testfile_{__ctr[0]}"


@pytest.mark.parametrize(
    "debug",
    [pytest.param(False, id="debug=false"), pytest.param(True, id="debug=true")],
)
@pytest.mark.parametrize(
    "cache_timeout",
    [pytest.param(0, id="cache_timeout=0"), pytest.param(1, id="cache_timeout=1")],
)
@pytest.mark.parametrize(
    "sync_rd",
    [pytest.param(True, id="sync_rd=true"), pytest.param(False, id="sync_rd=false")],
)
@pytest.mark.parametrize(
    "multiconn",
    [
        pytest.param(True, id="multiconn=true"),
        pytest.param(False, id="multiconn=false"),
    ],
)
def test_sshfs(
    tmpdir, debug: bool, cache_timeout: int, sync_rd: bool, multiconn: bool, capfd
) -> None:

    # Avoid false positives from debug messages
    # if debug:
    #    capfd.register_output(r'^   unique: [0-9]+, error: -[0-9]+ .+$',
    #                          count=0)

    # Avoid false positives from storing key for localhost
    capfd.register_output(r"^Warning: Permanently added 'localhost' .+", count=0)

    # Test if we can ssh into localhost without password
    try:
        res = subprocess.call(
            [
                "ssh",
                "-o",
                "StrictHostKeyChecking=no",
                "-o",
                "KbdInteractiveAuthentication=no",
                "-o",
                "ChallengeResponseAuthentication=no",
                "-o",
                "PasswordAuthentication=no",
                "localhost",
                "--",
                "true",
            ],
            stdin=subprocess.DEVNULL,
            timeout=10,
        )
    except subprocess.TimeoutExpired:
        res = 1
    if res != 0:
        pytest.fail("Unable to ssh into localhost without password prompt.")

    mnt_dir = str(tmpdir.mkdir("mnt"))
    src_dir = str(tmpdir.mkdir("src"))

    cmdline = base_cmdline + [
        pjoin(basename, "sshfs"),
        "-f",
        f"localhost:{src_dir}",
        mnt_dir,
    ]
    if debug:
        cmdline += ["-o", "sshfs_debug"]

    if sync_rd:
        cmdline += ["-o", "sync_readdir"]

    # SSHFS Cache
    if cache_timeout == 0:
        cmdline += ["-o", "dir_cache=no"]
    else:
        cmdline += ["-o", f"dcache_timeout={cache_timeout}", "-o", "dir_cache=yes"]

    # FUSE Cache
    cmdline += ["-o", "entry_timeout=0", "-o", "attr_timeout=0"]

    if multiconn:
        cmdline += ["-o", "max_conns=3"]

    new_env = dict(os.environ)  # copy, don't modify

    # Abort on warnings from glib
    new_env["G_DEBUG"] = "fatal-warnings"

    mount_process = subprocess.Popen(cmdline, env=new_env)
    try:
        wait_for_mount(mount_process, mnt_dir)

        tst_statvfs(mnt_dir)
        tst_readdir(src_dir, mnt_dir)
        tst_open_read(src_dir, mnt_dir)
        tst_open_write(src_dir, mnt_dir)
        tst_append(src_dir, mnt_dir)
        tst_seek(src_dir, mnt_dir)
        tst_create(mnt_dir)
        tst_passthrough(src_dir, mnt_dir, cache_timeout)
        tst_mkdir(mnt_dir)
        tst_rmdir(src_dir, mnt_dir, cache_timeout)
        tst_unlink(src_dir, mnt_dir, cache_timeout)
        tst_symlink(mnt_dir)
        if os.getuid() == 0:
            tst_chown(mnt_dir)

        # SSHFS only supports one second resolution when setting
        # file timestamps.
        tst_utimens(mnt_dir, tol=1)
        tst_utimens_now(mnt_dir)

        tst_link(mnt_dir, cache_timeout)
        tst_truncate_path(mnt_dir)
        tst_truncate_fd(mnt_dir)
        tst_open_unlink(mnt_dir)
    except Exception as exc:
        cleanup(mount_process, mnt_dir)
        raise exc
    else:
        umount(mount_process, mnt_dir)


def tst_unlink(src_dir, mnt_dir, cache_timeout):
    name = name_generator()
    fullname = mnt_dir + "/" + name
    with open(pjoin(src_dir, name), "wb") as fh:
        fh.write(b"hello")
    if cache_timeout:
        safe_sleep(cache_timeout + 1)
    assert name in os.listdir(mnt_dir)
    os.unlink(fullname)
    with pytest.raises(OSError) as exc_info:
        os.stat(fullname)
    assert exc_info.value.errno == errno.ENOENT
    assert name not in os.listdir(mnt_dir)
    assert name not in os.listdir(src_dir)


def tst_mkdir(mnt_dir):
    dirname = name_generator()
    fullname = mnt_dir + "/" + dirname
    os.mkdir(fullname)
    fstat = os.stat(fullname)
    assert stat.S_ISDIR(fstat.st_mode)
    assert os.listdir(fullname) == []
    assert fstat.st_nlink in (1, 2)
    assert dirname in os.listdir(mnt_dir)


def tst_rmdir(src_dir, mnt_dir, cache_timeout):
    name = name_generator()
    fullname = mnt_dir + "/" + name
    os.mkdir(pjoin(src_dir, name))
    if cache_timeout:
        safe_sleep(cache_timeout + 1)
    assert name in os.listdir(mnt_dir)
    os.rmdir(fullname)
    with pytest.raises(OSError) as exc_info:
        os.stat(fullname)
    assert exc_info.value.errno == errno.ENOENT
    assert name
Download .txt
gitextract_ux8sc0nm/

├── .appveyor.yml
├── .dir-locals.el
├── .git-blame-ignore-revs
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── issue-report.md
│   └── workflows/
│       └── build-ubuntu.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .travis.yml
├── AUTHORS
├── COPYING
├── ChangeLog.rst
├── README.md
├── cache.c
├── cache.h
├── compat/
│   ├── darwin_compat.c
│   └── darwin_compat.h
├── make_release_tarball.sh
├── meson.build
├── sshfs.c
├── sshfs.rst
├── test/
│   ├── .gitignore
│   ├── appveyor-build.sh
│   ├── conftest.py
│   ├── lint.sh
│   ├── lsan_suppress.txt
│   ├── meson.build
│   ├── pytest.ini
│   ├── test_sshfs.py
│   ├── travis-build.sh
│   ├── travis-install.sh
│   ├── util.py
│   └── wrong_command.c
└── utils/
    └── install_helper.sh
Download .txt
SYMBOL INDEX (294 symbols across 9 files)

FILE: cache.c
  type cache (line 24) | struct cache {
  type cache (line 39) | struct cache
  type node (line 41) | struct node {
  type readdir_handle (line 51) | struct readdir_handle {
  type file_handle (line 59) | struct file_handle {
  type cache_dirent (line 67) | struct cache_dirent {
  function free_node (line 72) | static void free_node(gpointer node_)
  function free_cache_dirent (line 81) | static void free_cache_dirent(gpointer data) {
  function cache_clean_entry (line 89) | static int cache_clean_entry(void *key_, struct node *node, time_t *now)
  function cache_clean (line 98) | static void cache_clean(void)
  type node (line 110) | struct node
  type node (line 112) | struct node
  function cache_purge (line 115) | static void cache_purge(const char *path)
  function cache_purge_parent (line 120) | static void cache_purge_parent(const char *path)
  function cache_invalidate (line 134) | void cache_invalidate(const char *path)
  function cache_invalidate_write (line 141) | static void cache_invalidate_write(const char *path)
  function cache_invalidate_dir (line 149) | static void cache_invalidate_dir(const char *path)
  function cache_del_children (line 157) | static int cache_del_children(const char *key, void *val_, const char *p...
  function cache_do_rename (line 166) | static void cache_do_rename(const char *from, const char *to)
  type node (line 178) | struct node
  type node (line 180) | struct node
  function cache_add_attr (line 189) | void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t...
  function cache_add_dir (line 205) | static void cache_add_dir(const char *path, GPtrArray *dir)
  function my_strnlen (line 222) | static size_t my_strnlen(const char *s, size_t maxsize)
  function cache_add_link (line 229) | static void cache_add_link(const char *path, const char *link, size_t size)
  function cache_get_attr (line 244) | static int cache_get_attr(const char *path, struct stat *stbuf)
  function cache_get_write_ctr (line 261) | uint64_t cache_get_write_ctr(void)
  type fuse_conn_info (line 272) | struct fuse_conn_info
  type fuse_config (line 273) | struct fuse_config
  function cache_getattr (line 284) | static int cache_getattr(const char *path, struct stat *stbuf,
  function cache_readlink (line 297) | static int cache_readlink(const char *path, char *buf, size_t size)
  function cache_opendir (line 322) | static int cache_opendir(const char *path, struct fuse_file_info *fi)
  function cache_releasedir (line 335) | static int cache_releasedir(const char *path, struct fuse_file_info *fi)
  function cache_dirfill (line 352) | static int cache_dirfill (void *buf, const char *name,
  function cache_readdir (line 378) | static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t fi...
  function cache_mknod (line 436) | static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
  function cache_mkdir (line 444) | static int cache_mkdir(const char *path, mode_t mode)
  function cache_unlink (line 452) | static int cache_unlink(const char *path)
  function cache_rmdir (line 460) | static int cache_rmdir(const char *path)
  function cache_symlink (line 468) | static int cache_symlink(const char *from, const char *to)
  function cache_rename (line 476) | static int cache_rename(const char *from, const char *to, unsigned int f...
  function cache_link (line 484) | static int cache_link(const char *from, const char *to)
  function cache_chmod (line 494) | static int cache_chmod(const char *path, mode_t mode,
  function cache_chown (line 503) | static int cache_chown(const char *path, uid_t uid, gid_t gid,
  function cache_utimens (line 512) | static int cache_utimens(const char *path, const struct timespec tv[2],
  function cache_write (line 521) | static int cache_write(const char *path, const char *buf, size_t size,
  function cache_create (line 530) | static int cache_create(const char *path, mode_t mode,
  function cache_truncate (line 539) | static int cache_truncate(const char *path, off_t size,
  function cache_fill (line 548) | static void cache_fill(struct fuse_operations *oper,
  type fuse_operations (line 583) | struct fuse_operations
  type fuse_operations (line 583) | struct fuse_operations
  type fuse_operations (line 585) | struct fuse_operations
  type fuse_opt (line 599) | struct fuse_opt
  type cache (line 600) | struct cache
  type cache (line 601) | struct cache
  type cache (line 602) | struct cache
  type cache (line 603) | struct cache
  type cache (line 604) | struct cache
  type cache (line 605) | struct cache
  type cache (line 606) | struct cache
  type cache (line 607) | struct cache
  type cache (line 609) | struct cache
  type cache (line 613) | struct cache
  type cache (line 614) | struct cache
  type cache (line 615) | struct cache
  type cache (line 616) | struct cache
  type cache (line 617) | struct cache
  type cache (line 618) | struct cache
  type cache (line 619) | struct cache
  type cache (line 620) | struct cache
  type cache (line 622) | struct cache
  function cache_parse_options (line 627) | int cache_parse_options(struct fuse_args *args)

FILE: cache.h
  type fuse_operations (line 12) | struct fuse_operations
  type fuse_operations (line 12) | struct fuse_operations
  type fuse_args (line 13) | struct fuse_args
  type stat (line 14) | struct stat

FILE: compat/darwin_compat.c
  function darwin_sem_init (line 42) | int
  function darwin_sem_destroy (line 75) | int
  function darwin_sem_getvalue (line 98) | int
  function darwin_sem_post (line 118) | int
  function darwin_sem_timedwait (line 144) | int
  function darwin_sem_trywait (line 187) | int
  function darwin_sem_wait (line 210) | int

FILE: compat/darwin_compat.h
  type darwin_sem_t (line 13) | typedef struct darwin_sem {
  type timespec (line 31) | struct timespec
  type darwin_sem_t (line 37) | typedef darwin_sem_t sem_t;

FILE: sshfs.c
  type conn (line 214) | struct conn {
  type buffer (line 225) | struct buffer {
  type dir_handle (line 231) | struct dir_handle {
  type list_head (line 236) | struct list_head {
  type request (line 241) | struct request
  type request (line 242) | struct request
  type request (line 244) | struct request {
  type sshfs_io (line 260) | struct sshfs_io {
  type read_req (line 266) | struct read_req {
  type read_chunk (line 274) | struct read_chunk {
  type sshfs_file (line 283) | struct sshfs_file {
  type conntab_entry (line 296) | struct conntab_entry {
  type sshfs (line 301) | struct sshfs {
  type sshfs (line 388) | struct sshfs
  type fuse_opt (line 474) | struct fuse_opt
  type fuse_opt (line 543) | struct fuse_opt
  function list_init (line 607) | static void list_init(struct list_head *head)
  function list_add (line 613) | static void list_add(struct list_head *new, struct list_head *head)
  function list_del (line 623) | static void list_del(struct list_head *entry)
  function list_empty (line 632) | static int list_empty(const struct list_head *head)
  function translate_id (line 639) | static inline int translate_id(uint32_t *id, GHashTable *map)
  function buf_init (line 655) | static inline void buf_init(struct buffer *buf, size_t size)
  function buf_free (line 669) | static inline void buf_free(struct buffer *buf)
  function buf_finish (line 674) | static inline void buf_finish(struct buffer *buf)
  function buf_clear (line 679) | static inline void buf_clear(struct buffer *buf)
  function buf_resize (line 685) | static void buf_resize(struct buffer *buf, size_t len)
  function buf_check_add (line 695) | static inline void buf_check_add(struct buffer *buf, size_t len)
  function buf_add_mem (line 707) | static inline void buf_add_mem(struct buffer *buf, const void *data,
  function buf_add_buf (line 713) | static inline void buf_add_buf(struct buffer *buf, const struct buffer *...
  function buf_add_uint8 (line 718) | static inline void buf_add_uint8(struct buffer *buf, uint8_t val)
  function buf_add_uint32 (line 723) | static inline void buf_add_uint32(struct buffer *buf, uint32_t val)
  function buf_add_uint64 (line 729) | static inline void buf_add_uint64(struct buffer *buf, uint64_t val)
  function buf_add_data (line 735) | static inline void buf_add_data(struct buffer *buf, const struct buffer ...
  function buf_add_string (line 741) | static inline void buf_add_string(struct buffer *buf, const char *str)
  function buf_add_path (line 749) | static inline void buf_add_path(struct buffer *buf, const char *path)
  function buf_check_get (line 777) | static int buf_check_get(struct buffer *buf, size_t len)
  function buf_get_mem (line 786) | static inline int buf_get_mem(struct buffer *buf, void *data, size_t len)
  function buf_get_uint8 (line 795) | static inline int buf_get_uint8(struct buffer *buf, uint8_t *val)
  function buf_get_uint32 (line 800) | static inline int buf_get_uint32(struct buffer *buf, uint32_t *val)
  function buf_get_uint64 (line 809) | static inline int buf_get_uint64(struct buffer *buf, uint64_t *val)
  function buf_get_data (line 821) | static inline int buf_get_data(struct buffer *buf, struct buffer *data)
  function buf_get_string (line 835) | static inline int buf_get_string(struct buffer *buf, char **str)
  function buf_get_attrs (line 845) | static int buf_get_attrs(struct buffer *buf, struct stat *stbuf, int *fl...
  function buf_get_statvfs (line 919) | static int buf_get_statvfs(struct buffer *buf, struct statvfs *stbuf)
  function buf_get_entries (line 961) | static int buf_get_entries(struct buffer *buf, void *dbuf,
  function ssh_add_arg (line 995) | static void ssh_add_arg(const char *arg)
  function pty_expect_loop (line 1002) | static int pty_expect_loop(struct conn *conn)
  type conn (line 1070) | struct conn
  type sshfs_file (line 1070) | struct sshfs_file
  type conntab_entry (line 1073) | struct conntab_entry
  type conn (line 1087) | struct conn
  function pty_master (line 1109) | static int pty_master(char **name)
  function replace_arg (line 1131) | static void replace_arg(char **argp, const char *newarg)
  function start_ssh (line 1141) | static int start_ssh(struct conn *conn)
  function connect_passive (line 1244) | static int connect_passive(struct conn *conn)
  function connect_to (line 1251) | static int connect_to(struct conn *conn, char *host, char *port)
  function connect_vsock (line 1297) | static int connect_vsock(struct conn *conn, char *vsock)
  function do_write (line 1351) | static int do_write(struct conn *conn, struct iovec *iov, size_t count)
  function sftp_get_id (line 1378) | static uint32_t sftp_get_id(void)
  function buf_to_iov (line 1384) | static void buf_to_iov(const struct buffer *buf, struct iovec *iov)
  function iov_length (line 1390) | static size_t iov_length(const struct iovec *iov, unsigned long nr_segs)
  function sftp_send_iov (line 1402) | static int sftp_send_iov(struct conn *conn, uint8_t type, uint32_t id,
  function do_read (line 1426) | static int do_read(struct conn *conn, struct buffer *buf)
  function sftp_read (line 1446) | static int sftp_read(struct conn *conn, uint8_t *type, struct buffer *buf)
  function request_free (line 1469) | static void request_free(struct request *req)
  function chunk_free (line 1481) | static void chunk_free(struct read_chunk *chunk)
  function chunk_put (line 1494) | static void chunk_put(struct read_chunk *chunk)
  function chunk_put_locked (line 1503) | static void chunk_put_locked(struct read_chunk *chunk)
  function clean_req (line 1510) | static int clean_req(void *key, struct request *req, gpointer user_data)
  function process_one_request (line 1527) | static int process_one_request(struct conn *conn)
  function close_conn (line 1595) | static void close_conn(struct conn *conn)
  type conn (line 1615) | struct conn
  function sftp_init_reply_ok (line 1638) | static int sftp_init_reply_ok(struct conn *conn, struct buffer *buf,
  function sftp_find_init_reply (line 1707) | static int sftp_find_init_reply(struct conn *conn, uint32_t *version)
  function sftp_init (line 1733) | static int sftp_init(struct conn *conn)
  function sftp_error_to_errno (line 1761) | static int sftp_error_to_errno(uint32_t error)
  function sftp_detect_uid (line 1776) | static void sftp_detect_uid(struct conn *conn)
  function sftp_check_root (line 1832) | static int sftp_check_root(struct conn *conn, const char *base_path)
  function connect_remote (line 1895) | static int connect_remote(struct conn *conn)
  function start_processing_thread (line 1918) | static int start_processing_thread(struct conn *conn)
  type fuse_conn_info (line 1956) | struct fuse_conn_info
  type fuse_config (line 1957) | struct fuse_config
  function sftp_request_wait (line 1982) | static int sftp_request_wait(struct request *req, uint8_t type,
  function sftp_request_send (line 2045) | static int sftp_request_send(struct conn *conn, uint8_t type, struct iov...
  function sftp_request_iov (line 2112) | static int sftp_request_iov(struct conn *conn, uint8_t type, struct iove...
  function sftp_request (line 2126) | static int sftp_request(struct conn *conn, uint8_t type, const struct bu...
  function sshfs_access (line 2135) | static int sshfs_access(const char *path, int mask)
  function count_components (line 2151) | static int count_components(const char *p)
  function strip_common (line 2163) | static void strip_common(const char **sp, const char **tp)
  function transform_symlink (line 2176) | static void transform_symlink(const char *path, char **linkp)
  function sshfs_readlink (line 2217) | static int sshfs_readlink(const char *path, char *linkbuf, size_t size)
  function sftp_readdir_send (line 2251) | static int sftp_readdir_send(struct conn *conn, struct request **req,
  function sshfs_req_pending (line 2261) | static int sshfs_req_pending(struct request *req)
  function sftp_readdir_async (line 2269) | static int sftp_readdir_async(struct conn *conn, struct buffer *handle,
  function sftp_readdir_sync (line 2347) | static int sftp_readdir_sync(struct conn *conn, struct buffer *handle,
  function sshfs_opendir (line 2366) | static int sshfs_opendir(const char *path, struct fuse_file_info *fi)
  function sshfs_readdir (line 2395) | static int sshfs_readdir(const char *path, void *dbuf, fuse_fill_dir_t f...
  function sshfs_releasedir (line 2415) | static int sshfs_releasedir(const char *path, struct fuse_file_info *fi)
  function sshfs_mkdir (line 2432) | static int sshfs_mkdir(const char *path, mode_t mode)
  function sshfs_mknod (line 2453) | static int sshfs_mknod(const char *path, mode_t mode, dev_t rdev)
  function sshfs_symlink (line 2485) | static int sshfs_symlink(const char *from, const char *to)
  function sshfs_unlink (line 2504) | static int sshfs_unlink(const char *path)
  function sshfs_rmdir (line 2516) | static int sshfs_rmdir(const char *path)
  function sshfs_do_rename (line 2528) | static int sshfs_do_rename(const char *from, const char *to)
  function sshfs_ext_posix_rename (line 2541) | static int sshfs_ext_posix_rename(const char *from, const char *to)
  function random_string (line 2555) | static void random_string(char *str, int length)
  function sshfs_rename (line 2563) | static int sshfs_rename(const char *from, const char *to, unsigned int f...
  function sshfs_link (line 2608) | static int sshfs_link(const char *from, const char *to)
  function sshfs_file_is_conn (line 2628) | static inline int sshfs_file_is_conn(struct sshfs_file *sf)
  type sshfs_file (line 2639) | struct sshfs_file
  type fuse_file_info (line 2639) | struct fuse_file_info
  type sshfs_file (line 2641) | struct sshfs_file
  function sshfs_chmod (line 2644) | static int sshfs_chmod(const char *path, mode_t mode,
  function sshfs_chown (line 2677) | static int sshfs_chown(const char *path, uid_t uid, gid_t gid,
  type fuse_file_info (line 2723) | struct fuse_file_info
  function sshfs_inc_modifver (line 2725) | static void sshfs_inc_modifver(void)
  function sshfs_utimens (line 2732) | static int sshfs_utimens(const char *path, const struct timespec tv[2],
  function sshfs_open_common (line 2770) | static int sshfs_open_common(const char *path, mode_t mode,
  function sshfs_open (line 2888) | static int sshfs_open(const char *path, struct fuse_file_info *fi)
  function sshfs_flush (line 2893) | static int sshfs_flush(const char *path, struct fuse_file_info *fi)
  function sshfs_fsync (line 2922) | static int sshfs_fsync(const char *path, int isdatasync,
  function sshfs_release (line 2945) | static int sshfs_release(const char *path, struct fuse_file_info *fi)
  function sshfs_read_end (line 2971) | static void sshfs_read_end(struct request *req)
  function sshfs_read_begin (line 3010) | static void sshfs_read_begin(struct request *req)
  type read_chunk (line 3016) | struct read_chunk
  type sshfs_file (line 3016) | struct sshfs_file
  type read_chunk (line 3019) | struct read_chunk
  type buffer (line 3020) | struct buffer
  type buffer (line 3030) | struct buffer
  type iovec (line 3031) | struct iovec
  type read_req (line 3032) | struct read_req
  function wait_chunk (line 3062) | static int wait_chunk(struct read_chunk *chunk, char *buf, size_t size)
  function sshfs_sync_read (line 3120) | static int sshfs_sync_read(struct sshfs_file *sf, char *buf, size_t size,
  function submit_read (line 3129) | static void submit_read(struct sshfs_file *sf, size_t size, off_t offset,
  type read_chunk (line 3143) | struct read_chunk
  type sshfs_file (line 3143) | struct sshfs_file
  type read_chunk (line 3145) | struct read_chunk
  function sshfs_async_read (line 3153) | static int sshfs_async_read(struct sshfs_file *sf, char *rbuf, size_t size,
  function sshfs_read (line 3203) | static int sshfs_read(const char *path, char *rbuf, size_t size, off_t o...
  function sshfs_write_begin (line 3218) | static void sshfs_write_begin(struct request *req)
  function sshfs_write_end (line 3224) | static void sshfs_write_end(struct request *req)
  function sshfs_async_write (line 3243) | static int sshfs_async_write(struct sshfs_file *sf, const char *wbuf,
  function sshfs_sync_write_begin (line 3273) | static void sshfs_sync_write_begin(struct request *req)
  function sshfs_sync_write_end (line 3279) | static void sshfs_sync_write_end(struct request *req)
  function sshfs_sync_write (line 3300) | static int sshfs_sync_write(struct sshfs_file *sf, const char *wbuf,
  function sshfs_write (line 3342) | static int sshfs_write(const char *path, const char *wbuf, size_t size,
  function sshfs_ext_statvfs (line 3363) | static int sshfs_ext_statvfs(const char *path, struct statvfs *stbuf)
  function sshfs_statfs (line 3383) | static int sshfs_statfs(const char *path, struct statvfs *buf)
  function sshfs_create (line 3401) | static int sshfs_create(const char *path, mode_t mode,
  function sshfs_truncate (line 3410) | static int sshfs_truncate(const char *path, off_t size,
  function sshfs_getattr (line 3444) | static int sshfs_getattr(const char *path, struct stat *stbuf,
  function sshfs_truncate_zero (line 3481) | static int sshfs_truncate_zero(const char *path)
  function calc_buf_size (line 3494) | static size_t calc_buf_size(off_t size, off_t offset)
  function sshfs_truncate_shrink (line 3499) | static int sshfs_truncate_shrink(const char *path, off_t size)
  function sshfs_truncate_extend (line 3545) | static int sshfs_truncate_extend(const char *path, off_t size,
  function sshfs_truncate_workaround (line 3580) | static int sshfs_truncate_workaround(const char *path, off_t size,
  function processing_init (line 3600) | static int processing_init(void)
  type fuse_operations (line 3626) | struct fuse_operations
  function usage (line 3655) | static void usage(const char *progname)
  function is_ssh_opt (line 3724) | static int is_ssh_opt(const char *arg)
  function sshfs_opt_proc (line 3739) | static int sshfs_opt_proc(void *data, const char *arg, int key,
  function workaround_opt_proc (line 3850) | static int workaround_opt_proc(void *data, const char *arg, int key,
  function parse_workarounds (line 3858) | static int parse_workarounds(void)
  function read_password (line 3881) | static int read_password(void)
  function set_ssh_command (line 3966) | static void set_ssh_command(void)
  function ssh_connect (line 4032) | static int ssh_connect(void)
  function parse_idmap_line (line 4057) | static void parse_idmap_line(char *line, const char* filename,
  function read_id_map (line 4103) | static void read_id_map(char *file, uint32_t *(*map_fn)(char *),
  type passwd (line 4174) | struct passwd
  type group (line 4198) | struct group
  function load_uid_map (line 4217) | static inline void load_uid_map(void)
  function load_gid_map (line 4222) | static inline void load_gid_map(void)
  function main (line 4230) | int main(int argc, char *argv[])

FILE: test/conftest.py
  function pytest_pyfunc_call (line 15) | def pytest_pyfunc_call(pyfuncitem):
  function pass_capfd (line 23) | def pass_capfd(request, capfd):
  function check_test_output (line 28) | def check_test_output(capfd):
  function register_output (line 71) | def register_output(self, pattern, count=1, flags=re.MULTILINE):
  function save_cap_fixtures (line 89) | def save_cap_fixtures(request, capfd):
  function pytest_runtest_call (line 109) | def pytest_runtest_call(item):

FILE: test/test_sshfs.py
  function name_generator (line 39) | def name_generator(__ctr=[0]) -> str:
  function test_sshfs (line 65) | def test_sshfs(
  function tst_unlink (line 169) | def tst_unlink(src_dir, mnt_dir, cache_timeout):
  function tst_mkdir (line 185) | def tst_mkdir(mnt_dir):
  function tst_rmdir (line 196) | def tst_rmdir(src_dir, mnt_dir, cache_timeout):
  function tst_symlink (line 211) | def tst_symlink(mnt_dir):
  function tst_create (line 222) | def tst_create(mnt_dir):
  function tst_chown (line 240) | def tst_chown(mnt_dir):
  function tst_open_read (line 260) | def tst_open_read(src_dir, mnt_dir):
  function tst_open_write (line 268) | def tst_open_write(src_dir, mnt_dir):
  function tst_append (line 279) | def tst_append(src_dir, mnt_dir):
  function tst_seek (line 292) | def tst_seek(src_dir, mnt_dir):
  function tst_open_unlink (line 307) | def tst_open_unlink(mnt_dir):
  function tst_statvfs (line 324) | def tst_statvfs(mnt_dir):
  function tst_link (line 328) | def tst_link(mnt_dir, cache_timeout):
  function tst_readdir (line 372) | def tst_readdir(src_dir, mnt_dir):
  function tst_truncate_path (line 397) | def tst_truncate_path(mnt_dir):
  function tst_truncate_fd (line 424) | def tst_truncate_fd(mnt_dir):
  function tst_utimens (line 447) | def tst_utimens(mnt_dir, tol=0):
  function tst_utimens_now (line 470) | def tst_utimens_now(mnt_dir):
  function tst_passthrough (line 483) | def tst_passthrough(src_dir, mnt_dir, cache_timeout):

FILE: test/util.py
  function os_create (line 13) | def os_create(name):
  function os_open (line 18) | def os_open(name, flags):
  function wait_for_mount (line 26) | def wait_for_mount(mount_process, mnt_dir, test_fn=os.path.ismount):
  function cleanup (line 38) | def cleanup(mount_process, mnt_dir):
  function umount (line 51) | def umount(mount_process, mnt_dir):
  function safe_sleep (line 69) | def safe_sleep(secs):
  function fuse_test_marker (line 84) | def fuse_test_marker():

FILE: test/wrong_command.c
  function main (line 3) | int main(void) {
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (228K chars).
[
  {
    "path": ".appveyor.yml",
    "chars": 970,
    "preview": "version: '{build}'\n\ninstall:\n\n# install WinFsp\n- appveyor DownloadFile https://github.com/billziss-gh/winfsp/releases/do"
  },
  {
    "path": ".dir-locals.el",
    "chars": 733,
    "preview": "((python-mode . ((indent-tabs-mode . nil)))\n (autoconf-mode . ((indent-tabs-mode . t)))\n (c-mode . ((c-file-style . \"str"
  },
  {
    "path": ".git-blame-ignore-revs",
    "chars": 41,
    "preview": "d54c7ecbd618afb4df524e0d96dec7fe7cc2935d\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue-report.md",
    "chars": 1108,
    "preview": "---\nname: Issue report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\nPLEASE READ BE"
  },
  {
    "path": ".github/workflows/build-ubuntu.yml",
    "chars": 1247,
    "preview": "name: build and test\non:\n  push:\n\n  pull_request:\n\n  workflow_dispatch: # this is a nice option that will enable a butto"
  },
  {
    "path": ".gitignore",
    "chars": 544,
    "preview": "#\n# NOTE! Don't add files that are generated in specific\n# subdirectories here. Add them in the \".gitignore\" file\n# in t"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 403,
    "preview": "# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n- repo: "
  },
  {
    "path": ".travis.yml",
    "chars": 439,
    "preview": "dist: focal\n\nlanguage: c\n\ncache:\n  - pip\n\naddons:\n  apt:\n    packages:\n    - shellcheck\n    - valgrind\n    - gcc\n    - c"
  },
  {
    "path": "AUTHORS",
    "chars": 2536,
    "preview": "Current Maintainer\n------------------\n\nNone.\n\n\nPast Maintainers\n----------------\n\nNikolaus Rath <Nikolaus@rath.org> (unt"
  },
  {
    "path": "COPYING",
    "chars": 18092,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "ChangeLog.rst",
    "chars": 8809,
    "preview": "Release 3.7.5 (2025-11-11)\n--------------------------\n\n* New maintainers added\n* Main documentation updates to using Mar"
  },
  {
    "path": "README.md",
    "chars": 4491,
    "preview": "\n# SSHFS\n\n\n## About\n\nSSHFS allows you to mount a remote filesystem using SFTP. Most SSH\nservers support and enable this "
  },
  {
    "path": "cache.c",
    "chars": 15977,
    "preview": "/*\n  Caching file system proxy\n  Copyright (C) 2004  Miklos Szeredi <miklos@szeredi.hu>\n\n  This program can be distribut"
  },
  {
    "path": "cache.h",
    "chars": 505,
    "preview": "/*\n    Caching file system proxy\n    Copyright (C) 2004  Miklos Szeredi <miklos@szeredi.hu>\n\n    This program can be dis"
  },
  {
    "path": "compat/darwin_compat.c",
    "chars": 6077,
    "preview": "/*\n * Copyright (c) 2006-2008 Amit Singh/Google Inc.\n * Copyright (c) 2012 Anatol Pomozov\n * Copyright (c) 2011-2013 Ben"
  },
  {
    "path": "compat/darwin_compat.h",
    "chars": 1376,
    "preview": "/*\n * Copyright (c) 2006-2008 Amit Singh/Google Inc.\n * Copyright (c) 2011-2013 Benjamin Fleischer\n */\n\n#ifndef _DARWIN_"
  },
  {
    "path": "make_release_tarball.sh",
    "chars": 779,
    "preview": "#!/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=\"$("
  },
  {
    "path": "meson.build",
    "chars": 2439,
    "preview": "project('sshfs', 'c', version: '3.7.5',\n        meson_version: '>= 0.40',\n        default_options: [ 'buildtype=debugopt"
  },
  {
    "path": "sshfs.c",
    "chars": 108042,
    "preview": "/*\n  SSH file system\n  Copyright (C) 2004  Miklos Szeredi <miklos@szeredi.hu>\n\n  This program can be distributed under t"
  },
  {
    "path": "sshfs.rst",
    "chars": 11901,
    "preview": "=======\n SSHFS\n=======\n\n---------------------------------------------\n filesystem client based on SSH\n------------------"
  },
  {
    "path": "test/.gitignore",
    "chars": 13,
    "preview": "__pycache__/\n"
  },
  {
    "path": "test/appveyor-build.sh",
    "chars": 98,
    "preview": "#!/bin/bash\nset -e\n\nmachine=$(uname -m)\nmkdir \"build-$machine\"\ncd \"build-$machine\"\nmeson ..\nninja\n"
  },
  {
    "path": "test/conftest.py",
    "chars": 3284,
    "preview": "import sys\nimport pytest\nimport time\nimport re\n\n# If a test fails, wait a moment before retrieving the captured stdout/s"
  },
  {
    "path": "test/lint.sh",
    "chars": 100,
    "preview": "#!/bin/bash\nset -e\npip3 install --user pre-commit\npre-commit run --all-files --show-diff-on-failure\n"
  },
  {
    "path": "test/lsan_suppress.txt",
    "chars": 372,
    "preview": "# Suppression file for address sanitizer.\n\n# There are some leaks in command line option parsing. They should be\n# fixed"
  },
  {
    "path": "test/meson.build",
    "chars": 487,
    "preview": "test_scripts = [ 'conftest.py', 'pytest.ini', 'test_sshfs.py',\n                 'util.py' ]\ncustom_target('test_scripts'"
  },
  {
    "path": "test/pytest.ini",
    "chars": 159,
    "preview": "[pytest]\naddopts = --verbose --assert=rewrite --tb=native -x -r a\n\nmarkers = uses_fuse: Mark to indicate that FUSE is av"
  },
  {
    "path": "test/test_sshfs.py",
    "chars": 14338,
    "preview": "#!/usr/bin/env python3\n\nif __name__ == \"__main__\":\n    import pytest\n    import sys\n\n    sys.exit(pytest.main([__file__]"
  },
  {
    "path": "test/travis-build.sh",
    "chars": 1053,
    "preview": "#!/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"
  },
  {
    "path": "test/travis-install.sh",
    "chars": 635,
    "preview": "#!/bin/sh\n\nset -e\n\n# Install fuse\nwget https://github.com/libfuse/libfuse/archive/master.zip\nunzip master.zip\ncd libfuse"
  },
  {
    "path": "test/util.py",
    "chars": 3441,
    "preview": "#!/usr/bin/env python3\nimport subprocess\nimport pytest\nimport os\nimport stat\nimport time\nfrom os.path import join as pjo"
  },
  {
    "path": "test/wrong_command.c",
    "chars": 211,
    "preview": "#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\""
  },
  {
    "path": "utils/install_helper.sh",
    "chars": 412,
    "preview": "#!/bin/sh\n#\n# Don't call this script. It is used internally by the Meson\n# build system. Thank you for your cooperation."
  }
]

About this extraction

This page contains the full source code of the libfuse/sshfs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (206.2 KB), approximately 60.4k tokens, and a symbol index with 294 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!