Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a2d988775 | |||
| 879f15a83b | |||
| 6678accb85 | |||
| 6893c3a51b | |||
| 29bb565ea6 | |||
| b6b23b7482 | |||
| bcd132f17c | |||
| 8711fa13a2 | |||
| 25b58aad4d | |||
| c71eea8e68 | |||
| afbdf92fa6 | |||
| daac34f7c8 | |||
| 88692b79eb | |||
| 035dcde9ba | |||
| cc14ae1c79 | |||
| 78f807b3dd | |||
| fd885f023c | |||
| 033a1d48f5 | |||
| 82285f9c28 | |||
| 43b2708186 | |||
| d749988c80 | |||
| 7921230f83 | |||
| ea60e3466e | |||
| 033a88349c | |||
| 1cd6995713 | |||
| 9e35c39ba8 | |||
| bf540dd68b | |||
| 882d3a0185 | |||
| 113882adda | |||
| ccb6821019 | |||
| ed0825440c | |||
| a9eb71cb1c | |||
| b1715a5a62 | |||
| 21fdcad6fb | |||
| ef94977c5a | |||
| d78a624756 | |||
| ddf1e42ce7 | |||
| 1a52814a4e | |||
| c9bf8ad451 | |||
| 5f767dec5b | |||
| 8bb9d33d16 | |||
| eadf7f104a | |||
| 1af815b7dc | |||
| 683b8c2454 | |||
| 70c8fd9031 | |||
| 47a580dd77 | |||
| e5f4fcaad7 | |||
| 8c97da5b16 | |||
| 551752c3a5 | |||
| c91eb9a9a9 | |||
| 103c6ba68b | |||
| 3aa3efcb52 | |||
| a2054a2b73 | |||
| fd02499b4c | |||
| c2715f7453 | |||
| 1abde6e779 | |||
| a181b9b60b | |||
| d54c7ecbd6 | |||
| 803e0e65cf |
@@ -0,0 +1 @@
|
||||
d54c7ecbd618afb4df524e0d96dec7fe7cc2935d
|
||||
@@ -11,7 +11,7 @@ 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 visibilty 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.
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
@@ -0,0 +1,149 @@
|
||||
name: platform builds
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
linux-compat:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: ubuntu-latest
|
||||
runner: ubuntu-latest
|
||||
run_tests: true
|
||||
- name: ubuntu-22.04
|
||||
runner: ubuntu-22.04
|
||||
run_tests: true
|
||||
- name: ubuntu-24.04-arm
|
||||
runner: ubuntu-24.04-arm
|
||||
run_tests: true
|
||||
- name: ubuntu-24.04-release
|
||||
runner: ubuntu-24.04
|
||||
buildtype: release
|
||||
- name: ubuntu-24.04-debug
|
||||
runner: ubuntu-24.04
|
||||
buildtype: debug
|
||||
- name: ubuntu-24.04-hardened
|
||||
runner: ubuntu-24.04
|
||||
extra_cflags: "-D_FORTIFY_SOURCE=3 -fstack-protector-strong"
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc ninja-build pkg-config libglib2.0-dev libfuse3-dev ${{ matrix.run_tests && 'openssh-server openssh-client fuse3' || '' }}
|
||||
pip3 install meson ${{ matrix.run_tests && 'pytest pytest-timeout' || '' }}
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
CFLAGS: ${{ matrix.extra_cflags || '' }}
|
||||
run: |
|
||||
meson setup build ${{ matrix.buildtype && format('--buildtype={0}', matrix.buildtype) || '' }}
|
||||
ninja -C build
|
||||
|
||||
- name: Setup SSH
|
||||
if: matrix.run_tests
|
||||
run: |
|
||||
chmod go-w ~
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -q -N ""
|
||||
cat ~/.ssh/id_ed25519.pub > ~/.ssh/authorized_keys
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
sudo sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
|
||||
sudo systemctl restart ssh || sudo service ssh restart
|
||||
ssh -o StrictHostKeyChecking=no -o BatchMode=yes localhost true
|
||||
|
||||
- name: Check FUSE availability
|
||||
if: matrix.run_tests
|
||||
run: |
|
||||
test -e /dev/fuse
|
||||
command -v fusermount3
|
||||
|
||||
- name: Run tests
|
||||
if: matrix.run_tests
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
cd build
|
||||
python3 -m pytest test/ --timeout=300 --maxfail=99 --junitxml=test-results.xml
|
||||
|
||||
- name: Upload test results
|
||||
if: matrix.run_tests && always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: test-results-${{ matrix.name }}
|
||||
path: |
|
||||
build/test-results.xml
|
||||
build/meson-logs/
|
||||
|
||||
- name: Upload build logs
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: failure()
|
||||
with:
|
||||
name: build-logs-${{ matrix.name }}
|
||||
path: build/meson-logs/
|
||||
|
||||
alpine-musl:
|
||||
name: alpine-musl
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Build in Alpine container
|
||||
run: |
|
||||
docker run --rm -v "$PWD:/work" -w /work alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d sh -euxc '
|
||||
apk add --no-cache gcc musl-dev meson ninja pkgconf glib-dev fuse3-dev
|
||||
meson setup build-alpine
|
||||
ninja -C build-alpine
|
||||
'
|
||||
|
||||
- name: Upload build logs
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: failure()
|
||||
with:
|
||||
name: build-logs-alpine-musl
|
||||
path: build-alpine/meson-logs/
|
||||
|
||||
freebsd:
|
||||
name: freebsd-14
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Build on FreeBSD
|
||||
uses: vmactions/freebsd-vm@d1e65811565151536c0c894fff74f06351ed26e6 # v1.4.5
|
||||
with:
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y fusefs-libs3 glib meson ninja pkgconf
|
||||
run: |
|
||||
meson setup build-freebsd
|
||||
ninja -C build-freebsd
|
||||
|
||||
- name: Upload build logs
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: failure()
|
||||
with:
|
||||
name: build-logs-freebsd
|
||||
path: build-freebsd/meson-logs/
|
||||
@@ -0,0 +1,143 @@
|
||||
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
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
name: ${{ matrix.compiler }} / ${{ matrix.buildtype }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
buildtype: [debugoptimized, release]
|
||||
include:
|
||||
- compiler: gcc
|
||||
cc: gcc
|
||||
- compiler: clang
|
||||
cc: clang
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc clang ninja-build libglib2.0-dev libfuse3-dev openssh-server openssh-client fuse3
|
||||
|
||||
- name: Install meson
|
||||
run: pip3 install meson pytest pytest-timeout
|
||||
|
||||
- name: Print tool versions
|
||||
run: |
|
||||
${{ matrix.cc }} --version
|
||||
meson --version
|
||||
|
||||
- name: Setup SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -q -N ""
|
||||
cat ~/.ssh/id_ed25519.pub > ~/.ssh/authorized_keys
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
sudo systemctl start ssh || sudo service ssh start
|
||||
ssh -o StrictHostKeyChecking=no -o BatchMode=yes localhost true
|
||||
|
||||
- name: Check FUSE availability
|
||||
run: |
|
||||
test -e /dev/fuse
|
||||
command -v fusermount3
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
CC: ${{ matrix.cc }}
|
||||
run: |
|
||||
meson setup build --buildtype=${{ matrix.buildtype }} -Dwerror=true
|
||||
ninja -C build
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: sshfs-${{ matrix.compiler }}-${{ matrix.buildtype }}
|
||||
path: build/sshfs
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Run tests
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
cd build
|
||||
python3 -m pytest --maxfail=99 --timeout=300 --junitxml=test-results.xml test/
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-${{ matrix.compiler }}-${{ matrix.buildtype }}
|
||||
path: |
|
||||
build/test-results.xml
|
||||
build/meson-logs/
|
||||
|
||||
strict-warnings:
|
||||
name: ${{ matrix.compiler }} / strict warnings
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- compiler: gcc
|
||||
cc: gcc
|
||||
extra_cflags: "-Wformat=2 -Wformat-security -Wundef -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wnull-dereference"
|
||||
- compiler: clang
|
||||
cc: clang
|
||||
extra_cflags: "-Wformat=2 -Wformat-security -Wundef -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wnull-dereference"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc clang ninja-build libglib2.0-dev libfuse3-dev
|
||||
|
||||
- name: Install meson
|
||||
run: pip3 install meson
|
||||
|
||||
- name: Print tool versions
|
||||
run: |
|
||||
${{ matrix.cc }} --version
|
||||
meson --version
|
||||
|
||||
- name: Build with strict warnings
|
||||
env:
|
||||
CC: ${{ matrix.cc }}
|
||||
CFLAGS: ${{ matrix.extra_cflags }}
|
||||
run: |
|
||||
meson setup build -Dwerror=true
|
||||
ninja -C build
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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
|
||||
+15
-5
@@ -1,11 +1,14 @@
|
||||
dist: focal
|
||||
sudo: enabled
|
||||
|
||||
language:
|
||||
- c
|
||||
language: c
|
||||
|
||||
cache:
|
||||
- pip
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- shellcheck
|
||||
- valgrind
|
||||
- gcc
|
||||
- clang
|
||||
@@ -16,6 +19,13 @@ addons:
|
||||
- meson
|
||||
- python3-pytest
|
||||
- libglib2.0-dev
|
||||
install: test/travis-install.sh
|
||||
script: test/travis-build.sh
|
||||
|
||||
install: test/travis-install.sh
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- name: Lint
|
||||
script: ./test/lint.sh
|
||||
install: skip
|
||||
- name: Build + Test
|
||||
script: test/travis-build.sh
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
Current Maintainer
|
||||
Current Maintainers
|
||||
------------------
|
||||
|
||||
Nikolaus Rath <Nikolaus@rath.org>
|
||||
Haoxi Tan <h4sh5@pm.me>
|
||||
Abhinav Agarwal <abhinavagarwal1996@gmail.com>
|
||||
|
||||
|
||||
|
||||
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>
|
||||
@@ -42,6 +47,8 @@ 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>
|
||||
@@ -51,6 +58,7 @@ 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>
|
||||
|
||||
+44
-7
@@ -1,3 +1,40 @@
|
||||
Release 3.7.6 (2026-05-30)
|
||||
--------------------------
|
||||
|
||||
* Added new maintainer: abhinavagarwal07 Abhinav Agarwal
|
||||
* Fixed critical vulnerability CVE-2026-47187 - Symlink Escape: Rogue SFTP Server to Local File Read/Write), credit to abhinavagarwal07
|
||||
* New -o contain_symlinks and -o no_contain_symlinks to control symlink containment behavior
|
||||
* Fixed high severity vulnerability CVE-2026-48711 - Improper Neutralization of Argument Delimiters in a Command ('Argument Injection'), credit to abhinavagarwal07
|
||||
* Fixed null-deref warning in tokenize_on_space, promote strict-warnings to required
|
||||
* Added a number of tests in CI, including rename, chmod, fsync, statvfs values, error paths, option coverage
|
||||
* Fixed malformed SFTP reply handling
|
||||
* Hardened Github Actions workflow with SHA pins, permissions, timeouts, and dependabot
|
||||
|
||||
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)
|
||||
--------------------------
|
||||
|
||||
@@ -25,7 +62,7 @@ Release 3.7.0 (2020-01-03)
|
||||
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)
|
||||
--------------------------
|
||||
|
||||
@@ -108,13 +145,13 @@ Release 3.1.0 (2017-08-04)
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
@@ -129,7 +166,7 @@ Release 3.0.0 (2017-07-08)
|
||||
* 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)
|
||||
------------------------
|
||||
|
||||
@@ -168,14 +205,14 @@ Release 2.4 (2012-03-08)
|
||||
------------------------
|
||||
|
||||
* New `slave` option.
|
||||
* New `idmap`, `uidmap` and `gidmap` options.
|
||||
* 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
|
||||
* Small improvements and bug fixes
|
||||
* Check mount point and options before connecting to ssh server
|
||||
* New 'delay_connect' option
|
||||
|
||||
@@ -188,7 +225,7 @@ Release 2.2 (2008-10-20)
|
||||
Release 2.1 (2008-07-11)
|
||||
------------------------
|
||||
|
||||
* Small improvements and bug fixes
|
||||
* Small improvements and bug fixes
|
||||
|
||||
Release 2.0 (2008-04-23)
|
||||
------------------------
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
|
||||
# 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>
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
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). 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 ask 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
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
First, download the latest SSHFS release from
|
||||
https://github.com/libfuse/sshfs/releases. On Linux and BSD, you will
|
||||
also need to install libfuse_ 3.1.0 or newer. On macOS, you need
|
||||
OSXFUSE_ instead. Finally, you need the Glib_ library with development
|
||||
headers (which should be available from your operating system's
|
||||
package manager).
|
||||
|
||||
To build and install, we recommend to use Meson_ (version 0.38 or
|
||||
newer) and Ninja_. 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`_ Python module)::
|
||||
|
||||
$ ninja
|
||||
$ python3 -m pytest test/ # optional, but recommended
|
||||
$ sudo ninja install
|
||||
|
||||
.. _libfuse: http://github.com/libfuse/libfuse
|
||||
.. _OSXFUSE: https://osxfuse.github.io/
|
||||
.. _Glib: https://developer.gnome.org/glib/stable/
|
||||
.. _Meson: http://mesonbuild.com/
|
||||
.. _Ninja: https://ninja-build.org/
|
||||
.. _`py.test`: http://www.pytest.org/
|
||||
|
||||
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.
|
||||
@@ -14,6 +14,7 @@
|
||||
#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
|
||||
@@ -40,7 +41,7 @@ static struct cache cache;
|
||||
struct node {
|
||||
struct stat stat;
|
||||
time_t stat_valid;
|
||||
char **dir;
|
||||
GPtrArray *dir;
|
||||
time_t dir_valid;
|
||||
char *link;
|
||||
time_t link_valid;
|
||||
@@ -63,13 +64,28 @@ struct file_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_;
|
||||
g_strfreev(node->dir);
|
||||
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_;
|
||||
@@ -186,13 +202,15 @@ void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr)
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
}
|
||||
|
||||
static void cache_add_dir(const char *path, char **dir)
|
||||
static void cache_add_dir(const char *path, GPtrArray *dir)
|
||||
{
|
||||
struct node *node;
|
||||
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
node = cache_get(path);
|
||||
g_strfreev(node->dir);
|
||||
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)
|
||||
@@ -256,7 +274,7 @@ static void *cache_init(struct fuse_conn_info *conn,
|
||||
{
|
||||
void *res;
|
||||
res = cache.next_oper->init(conn, cfg);
|
||||
|
||||
|
||||
// Cache requires a path for each request
|
||||
cfg->nullpath_ok = 0;
|
||||
|
||||
@@ -318,9 +336,9 @@ 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);
|
||||
@@ -341,7 +359,10 @@ static int cache_dirfill (void *buf, const char *name,
|
||||
ch = (struct readdir_handle*) buf;
|
||||
err = ch->filler(ch->buf, name, stbuf, off, flags);
|
||||
if (!err) {
|
||||
g_ptr_array_add(ch->dir, g_strdup(name));
|
||||
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;
|
||||
@@ -361,19 +382,20 @@ static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
|
||||
struct readdir_handle ch;
|
||||
struct file_handle *cfi;
|
||||
int err;
|
||||
char **dir;
|
||||
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(dir = node->dir; *dir != NULL; dir++)
|
||||
// FIXME: What about st_mode?
|
||||
filler(buf, *dir, NULL, 0, 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;
|
||||
}
|
||||
@@ -391,22 +413,22 @@ static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
|
||||
}
|
||||
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 = (char **) ch.dir->pdata;
|
||||
dir = ch.dir;
|
||||
if (!err) {
|
||||
cache_add_dir(path, dir);
|
||||
} else {
|
||||
g_strfreev(dir);
|
||||
g_ptr_array_free(dir, TRUE);
|
||||
}
|
||||
g_ptr_array_free(ch.dir, FALSE);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1,364 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU LGPL.
|
||||
See the file COPYING.LIB
|
||||
*/
|
||||
|
||||
#include "fuse_opt.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
struct fuse_opt_context {
|
||||
void *data;
|
||||
const struct fuse_opt *opt;
|
||||
fuse_opt_proc_t proc;
|
||||
int argctr;
|
||||
int argc;
|
||||
char **argv;
|
||||
struct fuse_args outargs;
|
||||
char *opts;
|
||||
int nonopt;
|
||||
};
|
||||
|
||||
void fuse_opt_free_args(struct fuse_args *args)
|
||||
{
|
||||
if (args && args->argv && args->allocated) {
|
||||
int i;
|
||||
for (i = 0; i < args->argc; i++)
|
||||
free(args->argv[i]);
|
||||
free(args->argv);
|
||||
args->argv = NULL;
|
||||
args->allocated = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int alloc_failed(void)
|
||||
{
|
||||
fprintf(stderr, "fuse: memory allocation failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
|
||||
{
|
||||
char **newargv;
|
||||
char *newarg;
|
||||
|
||||
assert(!args->argv || args->allocated);
|
||||
|
||||
newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
|
||||
newarg = newargv ? strdup(arg) : NULL;
|
||||
if (!newargv || !newarg)
|
||||
return alloc_failed();
|
||||
|
||||
args->argv = newargv;
|
||||
args->allocated = 1;
|
||||
args->argv[args->argc++] = newarg;
|
||||
args->argv[args->argc] = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
|
||||
{
|
||||
assert(pos <= args->argc);
|
||||
if (fuse_opt_add_arg(args, arg) == -1)
|
||||
return -1;
|
||||
|
||||
if (pos != args->argc - 1) {
|
||||
char *newarg = args->argv[args->argc - 1];
|
||||
memmove(&args->argv[pos + 1], &args->argv[pos],
|
||||
sizeof(char *) * (args->argc - pos - 1));
|
||||
args->argv[pos] = newarg;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int next_arg(struct fuse_opt_context *ctx, const char *opt)
|
||||
{
|
||||
if (ctx->argctr + 1 >= ctx->argc) {
|
||||
fprintf(stderr, "fuse: missing argument after `%s'\n", opt);
|
||||
return -1;
|
||||
}
|
||||
ctx->argctr++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_arg(struct fuse_opt_context *ctx, const char *arg)
|
||||
{
|
||||
return fuse_opt_add_arg(&ctx->outargs, arg);
|
||||
}
|
||||
|
||||
int fuse_opt_add_opt(char **opts, const char *opt)
|
||||
{
|
||||
char *newopts;
|
||||
if (!*opts)
|
||||
newopts = strdup(opt);
|
||||
else {
|
||||
unsigned oldlen = strlen(*opts);
|
||||
newopts = realloc(*opts, oldlen + 1 + strlen(opt) + 1);
|
||||
if (newopts) {
|
||||
newopts[oldlen] = ',';
|
||||
strcpy(newopts + oldlen + 1, opt);
|
||||
}
|
||||
}
|
||||
if (!newopts)
|
||||
return alloc_failed();
|
||||
|
||||
*opts = newopts;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_opt(struct fuse_opt_context *ctx, const char *opt)
|
||||
{
|
||||
return fuse_opt_add_opt(&ctx->opts, opt);
|
||||
}
|
||||
|
||||
static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
|
||||
int iso)
|
||||
{
|
||||
if (key == FUSE_OPT_KEY_DISCARD)
|
||||
return 0;
|
||||
|
||||
if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
|
||||
int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
|
||||
if (res == -1 || !res)
|
||||
return res;
|
||||
}
|
||||
if (iso)
|
||||
return add_opt(ctx, arg);
|
||||
else
|
||||
return add_arg(ctx, arg);
|
||||
}
|
||||
|
||||
static int match_template(const char *t, const char *arg, unsigned *sepp)
|
||||
{
|
||||
int arglen = strlen(arg);
|
||||
const char *sep = strchr(t, '=');
|
||||
sep = sep ? sep : strchr(t, ' ');
|
||||
if (sep && (!sep[1] || sep[1] == '%')) {
|
||||
int tlen = sep - t;
|
||||
if (sep[0] == '=')
|
||||
tlen ++;
|
||||
if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
|
||||
*sepp = sep - t;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (strcmp(t, arg) == 0) {
|
||||
*sepp = 0;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
|
||||
const char *arg, unsigned *sepp)
|
||||
{
|
||||
for (; opt && opt->templ; opt++)
|
||||
if (match_template(opt->templ, arg, sepp))
|
||||
return opt;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
|
||||
{
|
||||
unsigned dummy;
|
||||
return find_opt(opts, opt, &dummy) ? 1 : 0;
|
||||
}
|
||||
|
||||
static int process_opt_param(void *var, const char *format, const char *param,
|
||||
const char *arg)
|
||||
{
|
||||
assert(format[0] == '%');
|
||||
if (format[1] == 's') {
|
||||
char *copy = strdup(param);
|
||||
if (!copy)
|
||||
return alloc_failed();
|
||||
|
||||
*(char **) var = copy;
|
||||
} else {
|
||||
if (sscanf(param, format, var) != 1) {
|
||||
fprintf(stderr, "fuse: invalid parameter in option `%s'\n", arg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_opt(struct fuse_opt_context *ctx,
|
||||
const struct fuse_opt *opt, unsigned sep,
|
||||
const char *arg, int iso)
|
||||
{
|
||||
if (opt->offset == -1U) {
|
||||
if (call_proc(ctx, arg, opt->value, iso) == -1)
|
||||
return -1;
|
||||
} else {
|
||||
void *var = ctx->data + opt->offset;
|
||||
if (sep && opt->templ[sep + 1]) {
|
||||
const char *param = arg + sep;
|
||||
if (opt->templ[sep] == '=')
|
||||
param ++;
|
||||
if (process_opt_param(var, opt->templ + sep + 1,
|
||||
param, arg) == -1)
|
||||
return -1;
|
||||
} else
|
||||
*(int *)var = opt->value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_opt_sep_arg(struct fuse_opt_context *ctx,
|
||||
const struct fuse_opt *opt, unsigned sep,
|
||||
const char *arg, int iso)
|
||||
{
|
||||
int res;
|
||||
char *newarg;
|
||||
char *param;
|
||||
|
||||
if (next_arg(ctx, arg) == -1)
|
||||
return -1;
|
||||
|
||||
param = ctx->argv[ctx->argctr];
|
||||
newarg = malloc(sep + strlen(param) + 1);
|
||||
if (!newarg)
|
||||
return alloc_failed();
|
||||
|
||||
memcpy(newarg, arg, sep);
|
||||
strcpy(newarg + sep, param);
|
||||
res = process_opt(ctx, opt, sep, newarg, iso);
|
||||
free(newarg);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
|
||||
{
|
||||
unsigned sep;
|
||||
const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
|
||||
if (opt) {
|
||||
for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
|
||||
int res;
|
||||
if (sep && opt->templ[sep] == ' ' && !arg[sep])
|
||||
res = process_opt_sep_arg(ctx, opt, sep, arg, iso);
|
||||
else
|
||||
res = process_opt(ctx, opt, sep, arg, iso);
|
||||
if (res == -1)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
} else
|
||||
return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
|
||||
}
|
||||
|
||||
static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
|
||||
{
|
||||
char *sep;
|
||||
|
||||
do {
|
||||
int res;
|
||||
sep = strchr(opts, ',');
|
||||
if (sep)
|
||||
*sep = '\0';
|
||||
res = process_gopt(ctx, opts, 1);
|
||||
if (res == -1)
|
||||
return -1;
|
||||
opts = sep + 1;
|
||||
} while (sep);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
|
||||
{
|
||||
int res;
|
||||
char *copy;
|
||||
const char *sep = strchr(opts, ',');
|
||||
if (!sep)
|
||||
return process_gopt(ctx, opts, 1);
|
||||
|
||||
copy = strdup(opts);
|
||||
if (!copy) {
|
||||
fprintf(stderr, "fuse: memory allocation failed\n");
|
||||
return -1;
|
||||
}
|
||||
res = process_real_option_group(ctx, copy);
|
||||
free(copy);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int process_one(struct fuse_opt_context *ctx, const char *arg)
|
||||
{
|
||||
if (ctx->nonopt || arg[0] != '-')
|
||||
return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
|
||||
else if (arg[1] == 'o') {
|
||||
if (arg[2])
|
||||
return process_option_group(ctx, arg + 2);
|
||||
else {
|
||||
if (next_arg(ctx, arg) == -1)
|
||||
return -1;
|
||||
|
||||
return process_option_group(ctx, ctx->argv[ctx->argctr]);
|
||||
}
|
||||
} else if (arg[1] == '-' && !arg[2]) {
|
||||
if (add_arg(ctx, arg) == -1)
|
||||
return -1;
|
||||
ctx->nonopt = ctx->outargs.argc;
|
||||
return 0;
|
||||
} else
|
||||
return process_gopt(ctx, arg, 0);
|
||||
}
|
||||
|
||||
static int opt_parse(struct fuse_opt_context *ctx)
|
||||
{
|
||||
if (ctx->argc) {
|
||||
if (add_arg(ctx, ctx->argv[0]) == -1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
|
||||
if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
|
||||
return -1;
|
||||
|
||||
if (ctx->opts) {
|
||||
if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
|
||||
fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
|
||||
return -1;
|
||||
}
|
||||
if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc) {
|
||||
free(ctx->outargs.argv[ctx->outargs.argc - 1]);
|
||||
ctx->outargs.argv[--ctx->outargs.argc] = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fuse_opt_parse(struct fuse_args *args, void *data,
|
||||
const struct fuse_opt opts[], fuse_opt_proc_t proc)
|
||||
{
|
||||
int res;
|
||||
struct fuse_opt_context ctx = {
|
||||
.data = data,
|
||||
.opt = opts,
|
||||
.proc = proc,
|
||||
};
|
||||
|
||||
if (!args || !args->argv || !args->argc)
|
||||
return 0;
|
||||
|
||||
ctx.argc = args->argc;
|
||||
ctx.argv = args->argv;
|
||||
|
||||
res = opt_parse(&ctx);
|
||||
if (res != -1) {
|
||||
struct fuse_args tmp = *args;
|
||||
*args = ctx.outargs;
|
||||
ctx.outargs = tmp;
|
||||
}
|
||||
free(ctx.opts);
|
||||
fuse_opt_free_args(&ctx.outargs);
|
||||
return res;
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#ifndef _FUSE_OPT_H_
|
||||
#define _FUSE_OPT_H_
|
||||
|
||||
/* This file defines the option parsing interface of FUSE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Option description
|
||||
*
|
||||
* This structure describes a single option, and and action associated
|
||||
* with it, in case it matches.
|
||||
*
|
||||
* More than one such match may occur, in which case the action for
|
||||
* each match is executed.
|
||||
*
|
||||
* There are three possible actions in case of a match:
|
||||
*
|
||||
* i) An integer (int or unsigned) variable determined by 'offset' is
|
||||
* set to 'value'
|
||||
*
|
||||
* ii) The processing function is called, with 'value' as the key
|
||||
*
|
||||
* iii) An integer (any) or string (char *) variable determined by
|
||||
* 'offset' is set to the value of an option parameter
|
||||
*
|
||||
* 'offset' should normally be either set to
|
||||
*
|
||||
* - 'offsetof(struct foo, member)' actions i) and iii)
|
||||
*
|
||||
* - -1 action ii)
|
||||
*
|
||||
* The 'offsetof()' macro is defined in the <stddef.h> header.
|
||||
*
|
||||
* The template determines which options match, and also have an
|
||||
* effect on the action. Normally the action is either i) or ii), but
|
||||
* if a format is present in the template, then action iii) is
|
||||
* performed.
|
||||
*
|
||||
* The types of templates are:
|
||||
*
|
||||
* 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only
|
||||
* themselves. Invalid values are "--" and anything beginning
|
||||
* with "-o"
|
||||
*
|
||||
* 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or
|
||||
* the relevant option in a comma separated option list
|
||||
*
|
||||
* 3) "bar=", "--foo=", etc. These are variations of 1) and 2)
|
||||
* which have a parameter
|
||||
*
|
||||
* 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform
|
||||
* action iii).
|
||||
*
|
||||
* 5) "-x ", etc. Matches either "-xparam" or "-x param" as
|
||||
* two separate arguments
|
||||
*
|
||||
* 6) "-x %s", etc. Combination of 4) and 5)
|
||||
*
|
||||
* If the format is "%s", memory is allocated for the string unlike
|
||||
* with scanf().
|
||||
*/
|
||||
struct fuse_opt {
|
||||
/** Matching template and optional parameter formatting */
|
||||
const char *templ;
|
||||
|
||||
/**
|
||||
* Offset of variable within 'data' parameter of fuse_opt_parse()
|
||||
* or -1
|
||||
*/
|
||||
unsigned long offset;
|
||||
|
||||
/**
|
||||
* Value to set the variable to, or to be passed as 'key' to the
|
||||
* processing function. Ignored if template has a format
|
||||
*/
|
||||
int value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Key option. In case of a match, the processing function will be
|
||||
* called with the specified key.
|
||||
*/
|
||||
#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
|
||||
|
||||
/**
|
||||
* Last option. An array of 'struct fuse_opt' must end with a NULL
|
||||
* template value
|
||||
*/
|
||||
#define FUSE_OPT_END { .templ = NULL }
|
||||
|
||||
/**
|
||||
* Argument list
|
||||
*/
|
||||
struct fuse_args {
|
||||
/** Argument count */
|
||||
int argc;
|
||||
|
||||
/** Argument vector. NULL terminated */
|
||||
char **argv;
|
||||
|
||||
/** Is 'argv' allocated? */
|
||||
int allocated;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializer for 'struct fuse_args'
|
||||
*/
|
||||
#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
|
||||
|
||||
/**
|
||||
* Key value passed to the processing function if an option did not
|
||||
* match any template
|
||||
*/
|
||||
#define FUSE_OPT_KEY_OPT -1
|
||||
|
||||
/**
|
||||
* Key value passed to the processing function for all non-options
|
||||
*
|
||||
* Non-options are the arguments beginning with a charater other than
|
||||
* '-' or all arguments after the special '--' option
|
||||
*/
|
||||
#define FUSE_OPT_KEY_NONOPT -2
|
||||
|
||||
/**
|
||||
* Special key value for options to keep
|
||||
*
|
||||
* Argument is not passed to processing function, but behave as if the
|
||||
* processing function returned 1
|
||||
*/
|
||||
#define FUSE_OPT_KEY_KEEP -3
|
||||
|
||||
/**
|
||||
* Special key value for options to discard
|
||||
*
|
||||
* Argument is not passed to processing function, but behave as if the
|
||||
* processing function returned zero
|
||||
*/
|
||||
#define FUSE_OPT_KEY_DISCARD -4
|
||||
|
||||
/**
|
||||
* Processing function
|
||||
*
|
||||
* This function is called if
|
||||
* - option did not match any 'struct fuse_opt'
|
||||
* - argument is a non-option
|
||||
* - option did match and offset was set to -1
|
||||
*
|
||||
* The 'arg' parameter will always contain the whole argument or
|
||||
* option including the parameter if exists. A two-argument option
|
||||
* ("-x foo") is always converted to single arguemnt option of the
|
||||
* form "-xfoo" before this function is called.
|
||||
*
|
||||
* Options of the form '-ofoo' are passed to this function without the
|
||||
* '-o' prefix.
|
||||
*
|
||||
* The return value of this function determines whether this argument
|
||||
* is to be inserted into the output argument vector, or discarded.
|
||||
*
|
||||
* @param data is the user data passed to the fuse_opt_parse() function
|
||||
* @param arg is the whole argument or option
|
||||
* @param key determines why the processing function was called
|
||||
* @param outargs the current output argument list
|
||||
* @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept
|
||||
*/
|
||||
typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
|
||||
struct fuse_args *outargs);
|
||||
|
||||
/**
|
||||
* Option parsing function
|
||||
*
|
||||
* If 'args' was returned from a previous call to fuse_opt_parse() or
|
||||
* it was constructed from
|
||||
*
|
||||
* A NULL 'args' is equivalent to an empty argument vector
|
||||
*
|
||||
* A NULL 'opts' is equivalent to an 'opts' array containing a single
|
||||
* end marker
|
||||
*
|
||||
* A NULL 'proc' is equivalent to a processing function always
|
||||
* returning '1'
|
||||
*
|
||||
* @param args is the input and output argument list
|
||||
* @param data is the user data
|
||||
* @param opts is the option description array
|
||||
* @param proc is the processing function
|
||||
* @return -1 on error, 0 on success
|
||||
*/
|
||||
int fuse_opt_parse(struct fuse_args *args, void *data,
|
||||
const struct fuse_opt opts[], fuse_opt_proc_t proc);
|
||||
|
||||
/**
|
||||
* Add an option to a comma separated option list
|
||||
*
|
||||
* @param opts is a pointer to an option list, may point to a NULL value
|
||||
* @param opt is the option to add
|
||||
* @return -1 on allocation error, 0 on success
|
||||
*/
|
||||
int fuse_opt_add_opt(char **opts, const char *opt);
|
||||
|
||||
/**
|
||||
* Add an argument to a NULL terminated argument vector
|
||||
*
|
||||
* @param args is the structure containing the current argument list
|
||||
* @param arg is the new argument to add
|
||||
* @return -1 on allocation error, 0 on success
|
||||
*/
|
||||
int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
|
||||
|
||||
/**
|
||||
* Add an argument at the specified position in a NULL terminated
|
||||
* argument vector
|
||||
*
|
||||
* Adds the argument to the N-th position. This is useful for adding
|
||||
* options at the beggining of the array which must not come after the
|
||||
* special '--' option.
|
||||
*
|
||||
* @param args is the structure containing the current argument list
|
||||
* @param pos is the position at which to add the argument
|
||||
* @param arg is the new argument to add
|
||||
* @return -1 on allocation error, 0 on success
|
||||
*/
|
||||
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
|
||||
|
||||
/**
|
||||
* Free the contents of argument list
|
||||
*
|
||||
* The structure itself is not freed
|
||||
*
|
||||
* @param args is the structure containing the argument list
|
||||
*/
|
||||
void fuse_opt_free_args(struct fuse_args *args);
|
||||
|
||||
|
||||
/**
|
||||
* Check if an option matches
|
||||
*
|
||||
* @param opts is the option description array
|
||||
* @param opt is the option to match
|
||||
* @return 1 if a match is found, 0 if not
|
||||
*/
|
||||
int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _FUSE_OPT_H_ */
|
||||
@@ -27,4 +27,3 @@ 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
|
||||
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
project('sshfs', 'c', version: '3.7.2',
|
||||
project('sshfs', 'c', version: '3.7.6',
|
||||
meson_version: '>= 0.40',
|
||||
default_options: [ 'buildtype=debugoptimized' ])
|
||||
|
||||
@@ -26,7 +26,7 @@ endif
|
||||
|
||||
|
||||
rst2man = find_program('rst2man', 'rst2man.py', required: false)
|
||||
|
||||
|
||||
cfg = configuration_data()
|
||||
|
||||
cfg.set_quoted('PACKAGE_VERSION', meson.project_version())
|
||||
@@ -34,8 +34,9 @@ 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/fuse_opt.c', 'compat/darwin_compat.c' ]
|
||||
sshfs_sources += [ 'compat/darwin_compat.c' ]
|
||||
include_dirs += [ include_directories('compat') ]
|
||||
else
|
||||
cfg.set_quoted('IDMAP_DEFAULT', 'none')
|
||||
@@ -69,4 +70,3 @@ meson.add_install_script('utils/install_helper.sh',
|
||||
|
||||
|
||||
subdir('test')
|
||||
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
#if !defined(__CYGWIN__)
|
||||
# include <fuse_lowlevel.h>
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
# include <fuse_darwin.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -50,6 +47,9 @@
|
||||
# include <libgen.h>
|
||||
# include <darwin_compat.h>
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
# include <linux/vm_sockets.h>
|
||||
#endif
|
||||
|
||||
#include "cache.h"
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
/*
|
||||
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
|
||||
@@ -158,11 +158,11 @@
|
||||
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
|
||||
@@ -280,6 +280,11 @@ struct read_chunk {
|
||||
struct sshfs_io sio;
|
||||
};
|
||||
|
||||
struct conntab_entry {
|
||||
unsigned refcount;
|
||||
struct conn *conn;
|
||||
};
|
||||
|
||||
struct sshfs_file {
|
||||
struct buffer handle;
|
||||
struct list_head write_reqs;
|
||||
@@ -289,15 +294,11 @@ struct sshfs_file {
|
||||
off_t next_pos;
|
||||
int is_seq;
|
||||
struct conn *conn;
|
||||
struct conntab_entry *ce;
|
||||
int connver;
|
||||
int modifver;
|
||||
};
|
||||
|
||||
struct conntab_entry {
|
||||
unsigned refcount;
|
||||
struct conn *conn;
|
||||
};
|
||||
|
||||
struct sshfs {
|
||||
char *directport;
|
||||
char *ssh_command;
|
||||
@@ -312,6 +313,7 @@ struct sshfs {
|
||||
int fstat_workaround;
|
||||
int createmode_workaround;
|
||||
int transform_symlinks;
|
||||
int contain_symlinks;
|
||||
int follow_symlinks;
|
||||
int no_check_root;
|
||||
int detect_uid;
|
||||
@@ -337,7 +339,6 @@ struct sshfs {
|
||||
int sync_readdir;
|
||||
int direct_io;
|
||||
int debug;
|
||||
int verbose;
|
||||
int foreground;
|
||||
int reconnect;
|
||||
int delay_connect;
|
||||
@@ -349,6 +350,7 @@ struct sshfs {
|
||||
pthread_mutex_t lock;
|
||||
unsigned int randseed;
|
||||
int max_conns;
|
||||
char *vsock;
|
||||
struct conn *conns;
|
||||
int ptyfd;
|
||||
int ptypassivefd;
|
||||
@@ -432,7 +434,7 @@ static const char *ssh_opts[] = {
|
||||
"ProxyCommand",
|
||||
"ProxyJump",
|
||||
"ProxyUseFdpass",
|
||||
"PubkeyAcceptedKeyTypes"
|
||||
"PubkeyAcceptedKeyTypes",
|
||||
"PubkeyAuthentication",
|
||||
"RekeyLimit",
|
||||
"RevokedHostKeys",
|
||||
@@ -490,9 +492,10 @@ static struct fuse_opt sshfs_opts[] = {
|
||||
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("contain_symlinks", contain_symlinks, 1),
|
||||
SSHFS_OPT("no_contain_symlinks", contain_symlinks, 0),
|
||||
SSHFS_OPT("follow_symlinks", follow_symlinks, 1),
|
||||
SSHFS_OPT("no_check_root", no_check_root, 1),
|
||||
SSHFS_OPT("password_stdin", password_stdin, 1),
|
||||
@@ -504,6 +507,7 @@ static struct fuse_opt sshfs_opts[] = {
|
||||
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),
|
||||
@@ -511,8 +515,6 @@ static struct fuse_opt sshfs_opts[] = {
|
||||
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),
|
||||
|
||||
@@ -523,7 +525,7 @@ static struct fuse_opt sshfs_opts[] = {
|
||||
/* 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),
|
||||
|
||||
@@ -1073,7 +1075,7 @@ static struct conn* get_conn(const struct sshfs_file *sf,
|
||||
|
||||
if (sshfs.max_conns == 1)
|
||||
return &sshfs.conns[0];
|
||||
|
||||
|
||||
if (sf != NULL)
|
||||
return sf->conn;
|
||||
|
||||
@@ -1174,7 +1176,7 @@ static int start_ssh(struct conn *conn)
|
||||
perror("failed to redirect input/output");
|
||||
_exit(1);
|
||||
}
|
||||
if (!sshfs.verbose && !sshfs.foreground && devnull != -1)
|
||||
if (!sshfs.foreground && devnull != -1)
|
||||
dup2(devnull, 2);
|
||||
|
||||
close(devnull);
|
||||
@@ -1222,6 +1224,13 @@ static int start_ssh(struct conn *conn)
|
||||
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));
|
||||
@@ -1249,6 +1258,10 @@ static int connect_to(struct conn *conn, char *host, char *port)
|
||||
|
||||
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) {
|
||||
@@ -1281,6 +1294,60 @@ static int connect_to(struct conn *conn, char *host, char *port)
|
||||
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;
|
||||
@@ -1378,23 +1445,24 @@ static int do_read(struct conn *conn, struct buffer *buf)
|
||||
|
||||
static int sftp_read(struct conn *conn, uint8_t *type, struct buffer *buf)
|
||||
{
|
||||
int res;
|
||||
int res = -1;
|
||||
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);
|
||||
if (do_read(conn, &buf2) == -1)
|
||||
goto out;
|
||||
if (buf_get_uint32(&buf2, &len) == -1)
|
||||
goto out;
|
||||
if (len < 1 || len > MAX_REPLY_LEN) {
|
||||
fprintf(stderr, "bad reply len: %u\n", len);
|
||||
goto out;
|
||||
}
|
||||
if (buf_get_uint8(&buf2, type) == -1)
|
||||
goto out;
|
||||
buf_init(buf, len - 1);
|
||||
res = do_read(conn, buf);
|
||||
|
||||
out:
|
||||
buf_free(&buf2);
|
||||
return res;
|
||||
}
|
||||
@@ -1467,10 +1535,14 @@ static int process_one_request(struct conn *conn)
|
||||
|
||||
buf_init(&buf, 0);
|
||||
res = sftp_read(conn, &type, &buf);
|
||||
if (res == -1)
|
||||
if (res == -1) {
|
||||
buf_free(&buf);
|
||||
return -1;
|
||||
if (buf_get_uint32(&buf, &id) == -1)
|
||||
}
|
||||
if (buf_get_uint32(&buf, &id) == -1) {
|
||||
buf_free(&buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&sshfs.lock);
|
||||
req = (struct request *)
|
||||
@@ -1833,6 +1905,8 @@ static int connect_remote(struct conn *conn)
|
||||
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)
|
||||
@@ -1906,7 +1980,7 @@ static void *sshfs_init(struct fuse_conn_info *conn,
|
||||
|
||||
// SFTP only supports 1-second time resolution
|
||||
conn->time_gran = 1000000000;
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -2104,6 +2178,36 @@ static void strip_common(const char **sp, const char **tp)
|
||||
} while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
|
||||
}
|
||||
|
||||
/*
|
||||
* Reject symlink targets that could escape the mount root: absolute
|
||||
* paths and any target containing a ".." component. Returns 1 if
|
||||
* the target is safe to expose to the kernel, 0 otherwise.
|
||||
*/
|
||||
static int symlink_target_is_contained(const char *target)
|
||||
{
|
||||
const char *p = target;
|
||||
|
||||
if (*p == '/')
|
||||
return 0;
|
||||
|
||||
while (*p) {
|
||||
const char *comp = p;
|
||||
|
||||
while (*p && *p != '/')
|
||||
p++;
|
||||
/*
|
||||
* Reject any ".." rather than try to normalize: in an
|
||||
* adversarial filesystem the server controls intermediate
|
||||
* components, so lexical normalization cannot be trusted.
|
||||
*/
|
||||
if (p - comp == 2 && comp[0] == '.' && comp[1] == '.')
|
||||
return 0;
|
||||
while (*p == '/')
|
||||
p++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void transform_symlink(const char *path, char **linkp)
|
||||
{
|
||||
const char *l = *linkp;
|
||||
@@ -2168,6 +2272,13 @@ static int sshfs_readlink(const char *path, char *linkbuf, size_t size)
|
||||
buf_get_string(&name, &link) != -1) {
|
||||
if (sshfs.transform_symlinks)
|
||||
transform_symlink(path, &link);
|
||||
if (sshfs.contain_symlinks &&
|
||||
!symlink_target_is_contained(link)) {
|
||||
free(link);
|
||||
buf_free(&name);
|
||||
buf_free(&buf);
|
||||
return -EPERM;
|
||||
}
|
||||
strncpy(linkbuf, link, size - 1);
|
||||
linkbuf[size - 1] = '\0';
|
||||
free(link);
|
||||
@@ -2371,13 +2482,13 @@ static int sshfs_mkdir(const char *path, mode_t 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;
|
||||
}
|
||||
|
||||
@@ -2394,7 +2505,7 @@ static int sshfs_mknod(const char *path, mode_t mode, dev_t rdev)
|
||||
|
||||
// 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);
|
||||
@@ -2532,7 +2643,7 @@ static int sshfs_rename(const char *from, const char *to, unsigned int flags)
|
||||
}
|
||||
pthread_mutex_unlock(&sshfs.lock);
|
||||
}
|
||||
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -2589,12 +2700,12 @@ static int sshfs_chmod(const char *path, mode_t mode,
|
||||
buf_init(&buf, 0);
|
||||
if (sf == NULL)
|
||||
buf_add_path(&buf, path);
|
||||
else
|
||||
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.
|
||||
@@ -2618,7 +2729,7 @@ static int sshfs_chown(const char *path, uid_t uid, gid_t gid,
|
||||
if (!sshfs_file_is_conn(sf))
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
||||
if (sshfs.remote_uid_detected) {
|
||||
if (uid == sshfs.local_uid)
|
||||
uid = sshfs.remote_uid;
|
||||
@@ -2635,7 +2746,7 @@ static int sshfs_chown(const char *path, uid_t uid, gid_t gid,
|
||||
buf_init(&buf, 0);
|
||||
if (sf == NULL)
|
||||
buf_add_path(&buf, path);
|
||||
else
|
||||
else
|
||||
buf_add_buf(&buf, &sf->handle);
|
||||
buf_add_uint32(&buf, SSH_FILEXFER_ATTR_UIDGID);
|
||||
buf_add_uint32(&buf, uid);
|
||||
@@ -2685,7 +2796,7 @@ static int sshfs_utimens(const char *path, const struct timespec tv[2],
|
||||
buf_init(&buf, 0);
|
||||
if (sf == NULL)
|
||||
buf_add_path(&buf, path);
|
||||
else
|
||||
else
|
||||
buf_add_buf(&buf, &sf->handle);
|
||||
buf_add_uint32(&buf, SSH_FILEXFER_ATTR_ACMODTIME);
|
||||
buf_add_uint32(&buf, asec);
|
||||
@@ -2698,6 +2809,12 @@ static int sshfs_utimens(const char *path, const struct timespec tv[2],
|
||||
return err;
|
||||
}
|
||||
|
||||
static gboolean conntab_entry_is(gpointer key, gpointer value, gpointer data)
|
||||
{
|
||||
(void) key;
|
||||
return value == data;
|
||||
}
|
||||
|
||||
static int sshfs_open_common(const char *path, mode_t mode,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
@@ -2740,7 +2857,7 @@ static int sshfs_open_common(const char *path, mode_t mode,
|
||||
|
||||
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);
|
||||
@@ -2750,7 +2867,7 @@ static int sshfs_open_common(const char *path, mode_t mode,
|
||||
pthread_mutex_lock(&sshfs.lock);
|
||||
sf->modifver= sshfs.modifver;
|
||||
if (sshfs.max_conns > 1) {
|
||||
ce = g_hash_table_lookup(sshfs.conntab, path);
|
||||
ce = g_hash_table_lookup(sshfs.conntab, path);
|
||||
if (!ce) {
|
||||
ce = g_malloc(sizeof(struct conntab_entry));
|
||||
ce->refcount = 0;
|
||||
@@ -2758,12 +2875,13 @@ static int sshfs_open_common(const char *path, mode_t mode,
|
||||
g_hash_table_insert(sshfs.conntab, g_strdup(path), ce);
|
||||
}
|
||||
sf->conn = ce->conn;
|
||||
sf->ce = ce;
|
||||
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->ce = NULL;
|
||||
}
|
||||
sf->connver = sf->conn->connver;
|
||||
pthread_mutex_unlock(&sshfs.lock);
|
||||
@@ -2803,10 +2921,12 @@ static int sshfs_open_common(const char *path, mode_t mode,
|
||||
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);
|
||||
sf->ce->refcount--;
|
||||
if (sf->ce->refcount == 0) {
|
||||
g_hash_table_foreach_remove(sshfs.conntab,
|
||||
conntab_entry_is,
|
||||
sf->ce);
|
||||
g_free(sf->ce);
|
||||
}
|
||||
pthread_mutex_unlock(&sshfs.lock);
|
||||
}
|
||||
@@ -2877,7 +2997,6 @@ 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);
|
||||
@@ -2885,12 +3004,13 @@ static int sshfs_release(const char *path, struct fuse_file_info *fi)
|
||||
buf_free(handle);
|
||||
chunk_put_locked(sf->readahead);
|
||||
if (sshfs.max_conns > 1) {
|
||||
struct conntab_entry *ce = sf->ce;
|
||||
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);
|
||||
if (ce->refcount == 0) {
|
||||
g_hash_table_foreach_remove(sshfs.conntab,
|
||||
conntab_entry_is, ce);
|
||||
g_free(ce);
|
||||
}
|
||||
pthread_mutex_unlock(&sshfs.lock);
|
||||
@@ -3317,7 +3437,7 @@ static int sshfs_statfs(const char *path, struct statvfs *buf)
|
||||
return sshfs_ext_statvfs(path, buf);
|
||||
|
||||
buf->f_namemax = 255;
|
||||
buf->f_bsize = sshfs.blksize;
|
||||
buf->f_bsize = sshfs.blksize ? sshfs.blksize : 4096;
|
||||
/*
|
||||
* df seems to use f_bsize instead of f_frsize, so make them
|
||||
* the same
|
||||
@@ -3354,7 +3474,7 @@ static int sshfs_truncate(const char *path, off_t size,
|
||||
sshfs_inc_modifver();
|
||||
if (sshfs.truncate_workaround)
|
||||
return sshfs_truncate_workaround(path, size, fi);
|
||||
|
||||
|
||||
buf_init(&buf, 0);
|
||||
|
||||
if (sf != NULL)
|
||||
@@ -3397,7 +3517,7 @@ static int sshfs_getattr(const char *path, struct stat *stbuf,
|
||||
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__
|
||||
@@ -3603,7 +3723,6 @@ static void usage(const char *progname)
|
||||
" -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"
|
||||
@@ -3641,10 +3760,14 @@ static void usage(const char *progname)
|
||||
" -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 contain_symlinks reject absolute symlinks and symlinks containing ..\n"
|
||||
" (enabled by default; disable with no_contain_symlinks)\n"
|
||||
" -o no_contain_symlinks allow all symlink targets including absolute and ..\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",
|
||||
@@ -3732,6 +3855,16 @@ static int sshfs_opt_proc(void *data, const char *arg, int key,
|
||||
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) {
|
||||
@@ -3864,7 +3997,7 @@ static char *tokenize_on_space(char *str)
|
||||
|
||||
start = pos;
|
||||
|
||||
while (pos && *pos != '\0') {
|
||||
while (*pos != '\0') {
|
||||
// break on space, but not on '\ '
|
||||
if (*pos == ' ' && *(pos - 1) != '\\') {
|
||||
break;
|
||||
@@ -3929,6 +4062,11 @@ static char *find_base_path(void)
|
||||
*d++ = '\0';
|
||||
s++;
|
||||
|
||||
if (sshfs.host[0] == '-') {
|
||||
fprintf(stderr, "invalid hostname '%s'\n", sshfs.host);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -4187,6 +4325,7 @@ int main(int argc, char *argv[])
|
||||
sshfs.max_conns = 1;
|
||||
sshfs.ptyfd = -1;
|
||||
sshfs.dir_cache = 1;
|
||||
sshfs.contain_symlinks = 1;
|
||||
sshfs.show_help = 0;
|
||||
sshfs.show_version = 0;
|
||||
sshfs.singlethread = 0;
|
||||
@@ -4237,6 +4376,12 @@ int main(int argc, char *argv[])
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (sshfs.transform_symlinks && sshfs.contain_symlinks)
|
||||
fprintf(stderr, "warning: transform_symlinks with "
|
||||
"contain_symlinks may reject transformed links "
|
||||
"containing '..' - consider adding "
|
||||
"-o no_contain_symlinks\n");
|
||||
|
||||
if (sshfs.idmap == IDMAP_USER)
|
||||
sshfs.detect_uid = 1;
|
||||
else if (sshfs.idmap == IDMAP_FILE) {
|
||||
@@ -4320,7 +4465,6 @@ int main(int argc, char *argv[])
|
||||
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)
|
||||
@@ -4331,6 +4475,8 @@ int main(int argc, char *argv[])
|
||||
if (sshfs.ssh_ver != 1 && strchr(sftp_server, '/') == NULL)
|
||||
ssh_add_arg("-s");
|
||||
|
||||
ssh_add_arg("--");
|
||||
ssh_add_arg(sshfs.host);
|
||||
ssh_add_arg(sftp_server);
|
||||
free(sshfs.sftp_server);
|
||||
|
||||
|
||||
@@ -156,6 +156,9 @@ Options
|
||||
-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
|
||||
@@ -172,6 +175,21 @@ Options
|
||||
``/foo/bar/com`` is a symlink to ``/foo/blub``, SSHFS will
|
||||
transform the link target to ``../blub`` on the client side.
|
||||
|
||||
-o contain_symlinks
|
||||
reject symlink targets that are absolute or contain ``..``
|
||||
components. When a blocked symlink is encountered, readlink
|
||||
returns EPERM. This is enabled by default to prevent a
|
||||
malicious server from inducing local file reads or writes
|
||||
through crafted symlink targets. Note that this is stricter
|
||||
than ``transform_symlinks``: the two options should not normally
|
||||
be combined, since transformed results often contain ``..``
|
||||
and would be rejected by containment.
|
||||
|
||||
-o no_contain_symlinks
|
||||
disable symlink containment and allow all symlink targets
|
||||
through unchanged, including absolute paths and paths
|
||||
containing ``..``. Only use this with fully trusted servers.
|
||||
|
||||
-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
|
||||
@@ -190,17 +208,17 @@ Options
|
||||
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.
|
||||
|
||||
@@ -209,17 +227,17 @@ Options
|
||||
when full.
|
||||
|
||||
-o direct_io
|
||||
This option disables the use of page cache (file content cache) in
|
||||
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
|
||||
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
|
||||
e.g. /proc filesystem
|
||||
|
||||
-o max_conns=N
|
||||
sets the maximum number of simultaneous SSH connections
|
||||
@@ -274,7 +292,7 @@ SSHFS hangs for no apparent reason
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In some cases, attempts to access the SSHFS mountpoint may freeze if
|
||||
no filesystem activity has occured for some time. This is typically
|
||||
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
|
||||
@@ -311,7 +329,7 @@ interrupted.
|
||||
Mounting from /etc/fstab
|
||||
========================
|
||||
|
||||
To mount an SSHFS filesystem from ``/etc/fstab``, simply use ``sshfs`
|
||||
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``).
|
||||
|
||||
@@ -335,9 +353,11 @@ https://github.com/libfuse/libfuse/issues.
|
||||
Authors
|
||||
=======
|
||||
|
||||
SSHFS is currently maintained by Nikolaus Rath <Nikolaus@rath.org>,
|
||||
SSHFS was maintained by Nikolaus Rath <Nikolaus@rath.org>,
|
||||
and was created by Miklos Szeredi <miklos@szeredi.hu>.
|
||||
|
||||
See https://github.com/libfuse/sshfs for new maintainer information.
|
||||
|
||||
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).
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
machine=$(uname -m)
|
||||
mkdir build-$machine
|
||||
cd build-$machine
|
||||
mkdir "build-$machine"
|
||||
cd "build-$machine"
|
||||
meson ..
|
||||
ninja
|
||||
|
||||
+44
-21
@@ -3,24 +3,28 @@ 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.mark.hookwrapper
|
||||
# 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'''
|
||||
"""Provide capfd object to UnitTest instances"""
|
||||
request.instance.capfd = capfd
|
||||
|
||||
|
||||
def check_test_output(capfd):
|
||||
(stdout, stderr) = capfd.readouterr()
|
||||
|
||||
@@ -31,39 +35,57 @@ def check_test_output(capfd):
|
||||
# Strip out false positives
|
||||
for (pattern, flags, count) in capfd.false_positives:
|
||||
cp = re.compile(pattern, flags)
|
||||
(stdout, cnt) = cp.subn('', stdout, count=count)
|
||||
(stdout, cnt) = cp.subn("", stdout, count=count)
|
||||
if count == 0 or count - cnt > 0:
|
||||
stderr = cp.sub('', stderr, count=count - cnt)
|
||||
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]+== ']
|
||||
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))
|
||||
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))
|
||||
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
|
||||
"""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.yield_fixture(autouse=True)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def save_cap_fixtures(request, capfd):
|
||||
global current_capfd
|
||||
capfd.false_positives = []
|
||||
@@ -71,7 +93,7 @@ def save_cap_fixtures(request, capfd):
|
||||
# Monkeypatch in a function to register false positives
|
||||
type(capfd).register_output = register_output
|
||||
|
||||
if request.config.getoption('capture') == 'no':
|
||||
if request.config.getoption("capture") == "no":
|
||||
capfd = None
|
||||
current_capfd = capfd
|
||||
bak = current_capfd
|
||||
@@ -82,6 +104,7 @@ def save_cap_fixtures(request, capfd):
|
||||
assert bak is current_capfd
|
||||
current_capfd = None
|
||||
|
||||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_runtest_call(item):
|
||||
capfd = current_capfd
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
pip3 install --user pre-commit
|
||||
pre-commit run --all-files --show-diff-on-failure
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
test_scripts = [ 'conftest.py', 'pytest.ini', 'test_sshfs.py',
|
||||
'util.py' ]
|
||||
'test_hostname_validation.py', 'util.py' ]
|
||||
custom_target('test_scripts', input: test_scripts,
|
||||
output: test_scripts, build_by_default: true,
|
||||
command: ['cp', '-fPp',
|
||||
command: ['cp', '-fPp',
|
||||
'@INPUT@', meson.current_build_dir() ])
|
||||
|
||||
# Provide something helpful when running 'ninja test'
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
[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
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tests for hostname validation — no FUSE mount required."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
sys.exit(pytest.main([__file__] + sys.argv[1:]))
|
||||
|
||||
import subprocess
|
||||
from util import base_cmdline, basename
|
||||
from os.path import join as pjoin
|
||||
|
||||
|
||||
def test_reject_option_injection_in_hostname(tmpdir):
|
||||
"""Bracketed source that resolves to a dash-prefixed host must be rejected."""
|
||||
|
||||
mnt_dir = str(tmpdir.mkdir("mnt"))
|
||||
malicious = "[-oProxyCommand=echo pwned]:/path"
|
||||
|
||||
cmdline = base_cmdline + [
|
||||
pjoin(basename, "sshfs"),
|
||||
"-f",
|
||||
malicious,
|
||||
mnt_dir,
|
||||
]
|
||||
res = subprocess.run(
|
||||
cmdline,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=10,
|
||||
text=True,
|
||||
)
|
||||
assert res.returncode != 0
|
||||
assert "invalid hostname" in res.stderr
|
||||
|
||||
|
||||
def test_reject_dash_host_after_doubledash(tmpdir):
|
||||
"""Non-bracketed dash-prefixed source after -- must also be rejected."""
|
||||
|
||||
mnt_dir = str(tmpdir.mkdir("mnt"))
|
||||
|
||||
cmdline = base_cmdline + [
|
||||
pjoin(basename, "sshfs"),
|
||||
"-f",
|
||||
"--",
|
||||
"-oProxyCommand=echo pwned:/path",
|
||||
mnt_dir,
|
||||
]
|
||||
res = subprocess.run(
|
||||
cmdline,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=10,
|
||||
text=True,
|
||||
)
|
||||
assert res.returncode != 0
|
||||
assert "invalid hostname" in res.stderr
|
||||
+740
-108
File diff suppressed because it is too large
Load Diff
+11
-7
@@ -6,36 +6,40 @@ set -e
|
||||
# that we still need to fix
|
||||
export ASAN_OPTIONS="detect_leaks=0"
|
||||
|
||||
export LSAN_OPTIONS="suppressions=$(pwd)/test/lsan_suppress.txt"
|
||||
export LSAN_OPTIONS="suppressions=${PWD}/test/lsan_suppress.txt"
|
||||
export CC
|
||||
|
||||
TEST_CMD="python3 -m pytest --maxfail=99 test/"
|
||||
|
||||
# Standard build with Valgrind
|
||||
for CC in gcc clang; do
|
||||
mkdir build-${CC}; cd build-${CC}
|
||||
if [ ${CC} == 'gcc-6' ]; then
|
||||
(
|
||||
mkdir "build-${CC}"; cd "build-${CC}"
|
||||
if [ "${CC}" == 'gcc-6' ]; then
|
||||
build_opts='-D b_lundef=false'
|
||||
else
|
||||
build_opts=''
|
||||
fi
|
||||
# shellcheck disable=SC2086
|
||||
meson -D werror=true ${build_opts} ../
|
||||
ninja
|
||||
|
||||
TEST_WITH_VALGRIND=true ${TEST_CMD}
|
||||
cd ..
|
||||
)
|
||||
done
|
||||
(cd build-$CC; sudo ninja install)
|
||||
(cd "build-${CC}"; sudo ninja install)
|
||||
|
||||
# Sanitized build
|
||||
CC=clang
|
||||
for san in undefined address; do
|
||||
mkdir build-${san}; cd build-${san}
|
||||
(
|
||||
mkdir "build-${san}"
|
||||
cd "build-${san}"
|
||||
# b_lundef=false is required to work around clang
|
||||
# bug, cf. https://groups.google.com/forum/#!topic/mesonbuild/tgEdAXIIdC4
|
||||
meson -D b_sanitize=${san} -D b_lundef=false -D werror=true ..
|
||||
ninja
|
||||
${TEST_CMD}
|
||||
sudo ninja install
|
||||
cd ..
|
||||
)
|
||||
done
|
||||
|
||||
@@ -13,7 +13,7 @@ ninja
|
||||
sudo ninja install
|
||||
test -e /usr/local/lib/pkgconfig || sudo mkdir /usr/local/lib/pkgconfig
|
||||
sudo mv /usr/local/lib/*/pkgconfig/* /usr/local/lib/pkgconfig/
|
||||
ls -d1 /usr/local/lib/*-linux-gnu | sudo tee /etc/ld.so.conf.d/usrlocal.conf
|
||||
printf '%s\n' /usr/local/lib/*-linux-gnu | sudo tee /etc/ld.so.conf.d/usrlocal.conf
|
||||
sudo ldconfig
|
||||
|
||||
# Setup ssh
|
||||
|
||||
+50
-25
@@ -5,34 +5,51 @@ import os
|
||||
import stat
|
||||
import time
|
||||
from os.path import join as pjoin
|
||||
from contextlib import contextmanager
|
||||
|
||||
basename = pjoin(os.path.dirname(__file__), '..')
|
||||
basename = pjoin(os.path.dirname(__file__), "..")
|
||||
|
||||
def wait_for_mount(mount_process, mnt_dir,
|
||||
test_fn=os.path.ismount):
|
||||
|
||||
def os_create(name):
|
||||
os.close(os.open(name, os.O_CREAT | os.O_RDWR))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def os_open(name, flags):
|
||||
fd = os.open(name, flags)
|
||||
try:
|
||||
yield fd
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def wait_for_mount(mount_process, mnt_dir, test_fn=os.path.ismount):
|
||||
elapsed = 0
|
||||
while elapsed < 30:
|
||||
if test_fn(mnt_dir):
|
||||
return True
|
||||
if mount_process.poll() is not None:
|
||||
pytest.fail('file system process terminated prematurely')
|
||||
pytest.fail("file system process terminated prematurely")
|
||||
time.sleep(0.1)
|
||||
elapsed += 0.1
|
||||
pytest.fail("mountpoint failed to come up")
|
||||
|
||||
|
||||
def cleanup(mount_process, mnt_dir):
|
||||
subprocess.call(['fusermount', '-z', '-u', mnt_dir],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.STDOUT)
|
||||
subprocess.call(
|
||||
["fusermount", "-z", "-u", mnt_dir],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
mount_process.terminate()
|
||||
try:
|
||||
mount_process.wait(1)
|
||||
except subprocess.TimeoutExpired:
|
||||
mount_process.kill()
|
||||
|
||||
|
||||
|
||||
def umount(mount_process, mnt_dir):
|
||||
subprocess.check_call(['fusermount3', '-z', '-u', mnt_dir ])
|
||||
subprocess.check_call(["fusermount3", "-z", "-u", mnt_dir])
|
||||
assert not os.path.ismount(mnt_dir)
|
||||
|
||||
# Give mount process a little while to terminate. Popen.wait(timeout)
|
||||
@@ -43,18 +60,19 @@ def umount(mount_process, mnt_dir):
|
||||
if code is not None:
|
||||
if code == 0:
|
||||
return
|
||||
pytest.fail('file system process terminated with code %s' % (code,))
|
||||
pytest.fail(f"file system process terminated with code {code}")
|
||||
time.sleep(0.1)
|
||||
elapsed += 0.1
|
||||
pytest.fail('mount process did not terminate')
|
||||
pytest.fail("mount process did not terminate")
|
||||
|
||||
|
||||
def safe_sleep(secs):
|
||||
'''Like time.sleep(), but sleep for at least *secs*
|
||||
"""Like time.sleep(), but sleep for at least *secs*
|
||||
|
||||
`time.sleep` may sleep less than the given period if a signal is
|
||||
received. This function ensures that we sleep for at least the
|
||||
desired time.
|
||||
'''
|
||||
"""
|
||||
|
||||
now = time.time()
|
||||
end = now + secs
|
||||
@@ -62,24 +80,27 @@ def safe_sleep(secs):
|
||||
time.sleep(end - now)
|
||||
now = time.time()
|
||||
|
||||
|
||||
def fuse_test_marker():
|
||||
'''Return a pytest.marker that indicates FUSE availability
|
||||
"""Return a pytest.marker that indicates FUSE availability
|
||||
|
||||
If system/user/environment does not support FUSE, return
|
||||
a `pytest.mark.skip` object with more details. If FUSE is
|
||||
supported, return `pytest.mark.uses_fuse()`.
|
||||
'''
|
||||
"""
|
||||
|
||||
skip = lambda x: pytest.mark.skip(reason=x)
|
||||
def skip(reason: str):
|
||||
return pytest.mark.skip(reason=reason)
|
||||
|
||||
with subprocess.Popen(['which', 'fusermount'], stdout=subprocess.PIPE,
|
||||
universal_newlines=True) as which:
|
||||
with subprocess.Popen(
|
||||
["which", "fusermount"], stdout=subprocess.PIPE, universal_newlines=True
|
||||
) as which:
|
||||
fusermount_path = which.communicate()[0].strip()
|
||||
|
||||
if not fusermount_path or which.returncode != 0:
|
||||
return skip("Can't find fusermount executable")
|
||||
|
||||
if not os.path.exists('/dev/fuse'):
|
||||
if not os.path.exists("/dev/fuse"):
|
||||
return skip("FUSE kernel module does not seem to be loaded")
|
||||
|
||||
if os.getuid() == 0:
|
||||
@@ -87,20 +108,24 @@ def fuse_test_marker():
|
||||
|
||||
mode = os.stat(fusermount_path).st_mode
|
||||
if mode & stat.S_ISUID == 0:
|
||||
return skip('fusermount executable not setuid, and we are not root.')
|
||||
return skip("fusermount executable not setuid, and we are not root.")
|
||||
|
||||
try:
|
||||
fd = os.open('/dev/fuse', os.O_RDWR)
|
||||
fd = os.open("/dev/fuse", os.O_RDWR)
|
||||
except OSError as exc:
|
||||
return skip('Unable to open /dev/fuse: %s' % exc.strerror)
|
||||
return skip(f"Unable to open /dev/fuse: {exc.strerror}")
|
||||
else:
|
||||
os.close(fd)
|
||||
|
||||
return pytest.mark.uses_fuse()
|
||||
|
||||
|
||||
# Use valgrind if requested
|
||||
if os.environ.get('TEST_WITH_VALGRIND', 'no').lower().strip() \
|
||||
not in ('no', 'false', '0'):
|
||||
base_cmdline = [ 'valgrind', '-q', '--' ]
|
||||
if os.environ.get("TEST_WITH_VALGRIND", "no").lower().strip() not in (
|
||||
"no",
|
||||
"false",
|
||||
"0",
|
||||
):
|
||||
base_cmdline = ["valgrind", "-q", "--"]
|
||||
else:
|
||||
base_cmdline = []
|
||||
|
||||
Reference in New Issue
Block a user