Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5393ddf795 | |||
| 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 | |||
| 9700b35370 | |||
| 6c1b92df81 | |||
| d18869a307 | |||
| dfd4cba385 | |||
| 8059e2ce63 | |||
| 9e01ffd161 | |||
| de0504e45b | |||
| 6625146af9 | |||
| 76ffb37444 | |||
| a96e521474 | |||
| 11df8874dc | |||
| a7e1038203 | |||
| 8340a67b31 | |||
| ab0e339e80 | |||
| b19e3b8001 | |||
| e910453156 | |||
| 28c22270f8 | |||
| 8822b60d9d | |||
| 0f3ab4fd4f | |||
| ddd968b025 | |||
| 2421675f3f | |||
| eade8feb05 | |||
| 4d866526dc | |||
| 6935b49eea | |||
| a548abd1f3 | |||
| a1e5f12c53 | |||
| 011986306b | |||
| e1a9050c61 | |||
| af63f35ac1 | |||
| 667cf34622 | |||
| 4c21d696e9 | |||
| 469c96b6d2 | |||
| c4485188c9 | |||
| 4dc9b612ec | |||
| 695cd8916f | |||
| 7364b73e80 | |||
| 1dbc89f959 | |||
| d299217510 | |||
| fda6c8f862 | |||
| 6b10b3c8c0 | |||
| d3c6c338ae | |||
| 93c052a3cd | |||
| 76d11fa6f4 | |||
| 9ced6ca681 | |||
| de11ec091e | |||
| 37b79d06f8 | |||
| 2f1147c7d6 | |||
| b5d1484314 | |||
| d0c5fa3ed6 | |||
| eac420791c | |||
| 6480b66bd6 | |||
| 30a2668b99 | |||
| 4ed15c4c04 | |||
| fc7a57f0dc | |||
| f0452119e0 | |||
| fb174704dd | |||
| 39663c8288 | |||
| 646a3c417b | |||
| 303126bad9 | |||
| f0a0cc2b7e | |||
| 2c7c667576 | |||
| 1846b685c4 | |||
| 7f56cfc602 | |||
| fd3aa6594f | |||
| 2731a001e5 | |||
| 608e7ab89c | |||
| a73dda48bd | |||
| 7c2550a7e4 | |||
| 71aa3b97f6 | |||
| 14636931ac | |||
| d193b19a8f | |||
| 949d76d1a2 | |||
| 848ff30d90 | |||
| 37c8c1c80b | |||
| 85b950d3c6 | |||
| b32c4a4ca1 | |||
| bc47eeb68c | |||
| e4de7d257f | |||
| 2a818faea5 | |||
| 48cf30d3e2 | |||
| 1f20b5dff5 | |||
| 5d64c08f69 | |||
| 586ad60414 | |||
| 0bb569557e | |||
| da3a3f5bd1 | |||
| d141ea44c1 | |||
| 6ac4046bee | |||
| 95c65a6246 | |||
| f8df458f23 | |||
| e2a4bc904e | |||
| 70acee8c4b | |||
| fc63c64602 | |||
| 1e6e067fdf | |||
| 44a56f0cac | |||
| 653de120c8 | |||
| 63300fd564 | |||
| 45710c81d7 | |||
| 161ece55a3 | |||
| db149d1d87 | |||
| 861d308a03 | |||
| 319f0dde18 | |||
| 82766d1093 | |||
| 6f6491cd13 | |||
| 03a693d295 | |||
| 55eb1f00e6 | |||
| 37b6969067 | |||
| ff6194b5c7 | |||
| af31551e18 | |||
| 82e899fb21 | |||
| 34146444ce | |||
| 6cc86fc0bd | |||
| 73e4cf79b0 | |||
| a202e2dcc8 | |||
| c711bf738f | |||
| 493081e8f6 | |||
| 3033dbc22e | |||
| 5f4619bac3 | |||
| b66ecb9c3a | |||
| 9ac5829b89 | |||
| 9cfeaf5120 | |||
| 56d6030f9c | |||
| 152617e53f | |||
| 0908f570dc | |||
| 5bc9ff371d | |||
| 4379773a9e | |||
| 6056149d16 | |||
| 53a14aeaf1 |
@@ -0,0 +1,19 @@
|
||||
version: '{build}'
|
||||
|
||||
install:
|
||||
|
||||
# install WinFsp
|
||||
- appveyor DownloadFile https://github.com/billziss-gh/winfsp/releases/download/v1.4B2/winfsp-1.4.18211.msi
|
||||
- for %%f in ("winfsp-*.msi") do start /wait msiexec /i %%f /qn INSTALLLEVEL=1000
|
||||
|
||||
# install FUSE for Cygwin (64-bit and 32-bit)
|
||||
- C:\cygwin64\bin\env.exe -i PATH=/bin bash "%ProgramFiles(x86)%\WinFsp\opt\cygfuse\install.sh"
|
||||
- C:\cygwin\bin\env.exe -i PATH=/bin bash "%ProgramFiles(x86)%\WinFsp\opt\cygfuse\install.sh"
|
||||
|
||||
# install additional Cygwin packages (64-bit and 32-bit)
|
||||
- C:\cygwin64\setup-x86_64.exe -qnNdO -R C:\cygwin64 -s http://cygwin.mirror.constant.com -l C:\cygwin64\var\cache\setup -P libglib2.0-devel -P meson
|
||||
- C:\cygwin\setup-x86.exe -qnNdO -R C:\cygwin -s http://cygwin.mirror.constant.com -l C:\cygwin\var\cache\setup -P libglib2.0-devel -P meson
|
||||
|
||||
build_script:
|
||||
- C:\cygwin64\bin\env.exe -i PATH=/bin bash test\appveyor-build.sh
|
||||
- C:\cygwin\bin\env.exe -i PATH=/bin bash test\appveyor-build.sh
|
||||
@@ -0,0 +1,25 @@
|
||||
((python-mode . ((indent-tabs-mode . nil)))
|
||||
(autoconf-mode . ((indent-tabs-mode . t)))
|
||||
(c-mode . ((c-file-style . "stroustrup")
|
||||
(indent-tabs-mode . t)
|
||||
(tab-width . 8)
|
||||
(c-basic-offset . 8)
|
||||
(c-file-offsets .
|
||||
((block-close . 0)
|
||||
(brace-list-close . 0)
|
||||
(brace-list-entry . 0)
|
||||
(brace-list-intro . +)
|
||||
(case-label . 0)
|
||||
(class-close . 0)
|
||||
(defun-block-intro . +)
|
||||
(defun-close . 0)
|
||||
(defun-open . 0)
|
||||
(else-clause . 0)
|
||||
(inclass . +)
|
||||
(label . 0)
|
||||
(statement . 0)
|
||||
(statement-block-intro . +)
|
||||
(statement-case-intro . +)
|
||||
(statement-cont . +)
|
||||
(substatement . +)
|
||||
(topmost-intro . 0))))))
|
||||
@@ -0,0 +1 @@
|
||||
d54c7ecbd618afb4df524e0d96dec7fe7cc2935d
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Issue report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
PLEASE READ BEFORE REPORTING AN ISSUE
|
||||
|
||||
SSHFS does not have any active, regular contributors or developers. The current maintainer continues to apply pull requests and tries to make regular releases, but unfortunately has no capacity to do any development beyond addressing high-impact issues. When reporting bugs, please understand that unless you are including a pull request or are reporting a critical issue, you will probably not get a response.
|
||||
|
||||
To prevent the issue tracker from being flooded with issues that no-one is intending to work on, and to give more visibility to critical issues that users should be aware of and that most urgently need attention, I will also close most bug reports once they've been inactive for a while.
|
||||
|
||||
Please note that this isn't meant to imply that you haven't found a bug - you most likely have and I'm grateful that you took the time to report it. Unfortunately, SSHFS is a purely volunteer driven project,
|
||||
and at the moment there simply aren't any volunteers.
|
||||
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
@@ -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,51 @@
|
||||
name: CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze C
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
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: Initialize CodeQL
|
||||
uses: github/codeql-action/init@bc0b696b4103f5fe60f15749af68a046868d511a # codeql-bundle-v2.25.4
|
||||
with:
|
||||
languages: c-cpp
|
||||
build-mode: manual
|
||||
queries: security-extended
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc ninja-build pkg-config libglib2.0-dev libfuse3-dev
|
||||
pip3 install meson
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
meson setup build --buildtype=debug
|
||||
ninja -C build sshfs
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@bc0b696b4103f5fe60f15749af68a046868d511a # codeql-bundle-v2.25.4
|
||||
+2
-2
@@ -6,8 +6,6 @@
|
||||
# NOTE! Please use 'git ls-files -i --exclude-standard'
|
||||
# command after changing this file, to see if there are
|
||||
# any tracked files which get ignored after the change.
|
||||
.*
|
||||
!.gitignore
|
||||
*.o
|
||||
*.lo
|
||||
*.la
|
||||
@@ -35,3 +33,5 @@ sshfs.1
|
||||
/.pc
|
||||
/patches
|
||||
/m4
|
||||
.deps/
|
||||
/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
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
dist: focal
|
||||
|
||||
language: c
|
||||
|
||||
cache:
|
||||
- pip
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- shellcheck
|
||||
- valgrind
|
||||
- gcc
|
||||
- clang
|
||||
- python-docutils
|
||||
- python3-pip
|
||||
- python3-setuptools
|
||||
- ninja-build
|
||||
- meson
|
||||
- python3-pytest
|
||||
- libglib2.0-dev
|
||||
|
||||
install: test/travis-install.sh
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- name: Lint
|
||||
script: ./test/lint.sh
|
||||
install: skip
|
||||
- name: Build + Test
|
||||
script: test/travis-build.sh
|
||||
@@ -1,33 +1,74 @@
|
||||
Current Maintainer
|
||||
------------------
|
||||
|
||||
Nikolaus Rath <Nikolaus@rath.org>
|
||||
None.
|
||||
|
||||
|
||||
Past Maintainers
|
||||
----------------
|
||||
|
||||
Nikolaus Rath <Nikolaus@rath.org> (until 05/2022)
|
||||
Miklos Szeredi <miklos@szeredi.hu> (until 12/2015)
|
||||
|
||||
|
||||
Contributors (autogenerated list)
|
||||
---------------------------------
|
||||
|
||||
a1346054 <36859588+a1346054@users.noreply.github.com>
|
||||
Alan Jenkins <alan.christopher.jenkins@gmail.com>
|
||||
Alexander Neumann <alexander@bumpern.de>
|
||||
Anatol Pomozov <anatol.pomozov@gmail.com>
|
||||
Andrew Stone <a@stne.dev>
|
||||
Antonio Rojas <arojas@archlinux.org>
|
||||
Benjamin Fleischer <fleiben@gmail.com>
|
||||
Berserker <berserker.troll@yandex.com>
|
||||
Bill Zissimopoulos <billziss@navimatics.com>
|
||||
bjoe2k4 <bjoe2k4@users.noreply.github.com>
|
||||
Brandon Carter <b-carter@users.noreply.github.com>
|
||||
Cam Cope <github@camcope.me>
|
||||
Chris Wolfe <cwolfe@chromium.org>
|
||||
Clayton G. Hobbs <clay@lakeserv.net>
|
||||
Daniel Lublin <daniel@lublin.se>
|
||||
Dominique Martinet <asmadeus@codewreck.org>
|
||||
DrDaveD <2129743+DrDaveD@users.noreply.github.com>
|
||||
Fabrice Fontaine <fontaine.fabrice@gmail.com>
|
||||
gala <gala132@users.noreply.github.com>
|
||||
Galen Getsov <4815620+ggetsov@users.noreply.github.com>
|
||||
George Vlahavas <vlahavas@gmail.com>
|
||||
G.raud Meyer <graud@gmx.com>
|
||||
harrim4n <git@harrim4n.com>
|
||||
Jakub Jelen <jjelen@redhat.com>
|
||||
jeg139 <54814784+jeg139@users.noreply.github.com>
|
||||
Josh Triplett <josh@joshtriplett.org>
|
||||
Julio Merino <jmmv@google.com>
|
||||
Julio Merino <jmmv@meroh.net>
|
||||
Junichi Uekawa <dancerj@gmail.com>
|
||||
Junichi Uekawa <dancer@netfort.gr.jp>
|
||||
kalvdans <github@kalvdans.no-ip.org>
|
||||
Kim Brose <kim.brose@rwth-aachen.de>
|
||||
Matthew Berginski <matthew.berginski@gmail.com>
|
||||
Michael Forney <mforney@mforney.org>
|
||||
Mike Kelly <mike@pair.com>
|
||||
Mike Salvatore <mike.s.salvatore@gmail.com>
|
||||
Miklos Szeredi <miklos@szeredi.hu>
|
||||
Miklos Szeredi <mszeredi@suse.cz>
|
||||
mssalvatore <mike.s.salvatore@gmail.com>
|
||||
Nikolaus Rath <Nikolaus@rath.org>
|
||||
Percy Jahn <email@percyjahn.de>
|
||||
Peter Belm <peterbelm@gmail.com>
|
||||
Peter Wienemann <peter.wienemann@uni-bonn.de>
|
||||
Qais Patankar <qaisjp@gmail.com>
|
||||
Quentin Rameau <quinq@fifth.space>
|
||||
Reid Wagner <wagnerreid@gmail.com>
|
||||
Rian Hunter <rian@alum.mit.edu>
|
||||
Rian Hunter <rianhunter@users.noreply.github.com>
|
||||
Samuel Murray <samuel.murray@outlook.com>
|
||||
S. D. Cloudt <s.d.cloudt@student.tue.nl>
|
||||
Simon Arlott <70171+nomis@users.noreply.github.com>
|
||||
smheidrich <smheidrich@weltenfunktion.de>
|
||||
sunwire <50745572+sunwire@users.noreply.github.com>
|
||||
Tim Harder <radhermit@gmail.com>
|
||||
Timo Savola <timo.savola@iki.fi>
|
||||
tpoindessous <thomas@poindessous.com>
|
||||
Viktor Szakats <vszakats@users.noreply.github.com>
|
||||
Zoltan Kuscsik <zoltan.kuscsik@linaro.org>
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
Unreleased Changes
|
||||
------------------
|
||||
|
||||
* Added support for building with Meson.
|
||||
* Added support for more SSH options.
|
||||
* Dropped support for the *nodelay* workaround - the last OpenSSH
|
||||
version for which this was useful was released in 2006.
|
||||
* Dropped support for the *nodelaysrv* workaround. The same effect
|
||||
(enabling NODELAY on the server side *and* enabling X11 forwarding)
|
||||
can be achieved by explicitly passing `-o ForwardX11`
|
||||
* Removed support for `-o workaround=all`. Workarounds should always
|
||||
enabled explicitly and only when needed. There is no point in always
|
||||
enabling a potentially changing set of workarounds.
|
||||
|
||||
Release 2.9 (2017-04-17)
|
||||
------------------------
|
||||
|
||||
* Improved support for Cygwin.
|
||||
* Various small bugfixes.
|
||||
|
||||
Release 2.8 (2016-06-22)
|
||||
------------------------
|
||||
|
||||
* Added support for the "fsync" extension.
|
||||
* Fixed a build problem with bitbake
|
||||
|
||||
Release 2.7 (2016-03-01)
|
||||
------------------------
|
||||
|
||||
* Integrated osxfuse's copy of sshfs, which means that sshfs now works
|
||||
on OS X out of the box.
|
||||
* Added -o cache_max_size=N option to let users tune the maximum size of
|
||||
the cache in number of entries.
|
||||
* Added -o cache_clean_interval=N and -o cache_min_clean_interval=N
|
||||
options to let users tune the cleaning behavior of the cache.
|
||||
|
||||
Release 2.6 (2015-01-28)
|
||||
------------------------
|
||||
|
||||
* New maintainer (Nikolaus Rath <Nikolaus@rath.org>)
|
||||
|
||||
Release 2.5 (2014-01-14)
|
||||
------------------------
|
||||
|
||||
* Some performance improvements for large directories.
|
||||
* New `disable_hardlink` option.
|
||||
* Various small bugfixes.
|
||||
|
||||
Release 2.4 (2012-03-08)
|
||||
------------------------
|
||||
|
||||
* New `slave` option.
|
||||
* New `idmap`, `uidmap` and `gidmap` options.
|
||||
* Various small bugfixes.
|
||||
|
||||
Release 2.3 (2011-07-01)
|
||||
------------------------
|
||||
|
||||
* Support hard link creation if server is OpenSSH 5.7 or later
|
||||
* Small improvements and bug fixes
|
||||
* Check mount point and options before connecting to ssh server
|
||||
* New 'delay_connect' option
|
||||
|
||||
Release 2.2 (2008-10-20)
|
||||
------------------------
|
||||
|
||||
* Handle numerical IPv6 addresses enclosed in square brackets
|
||||
* Handle commas in usernames
|
||||
|
||||
Release 2.1 (2008-07-11)
|
||||
------------------------
|
||||
|
||||
* Small improvements and bug fixes
|
||||
|
||||
Release 2.0 (2008-04-23)
|
||||
------------------------
|
||||
|
||||
* Support password authentication with pam_mount
|
||||
|
||||
* Support atomic renames if server is OpenSSH 4.9 or later
|
||||
|
||||
* Support getting disk usage if server is OpenSSH 5.1 or later
|
||||
|
||||
* Small enhancements and bug fixes
|
||||
|
||||
What is new in 1.9
|
||||
------------------
|
||||
|
||||
* Fix a serious bug, that could result in sshfs hanging, crashing, or
|
||||
reporting out-of-memory
|
||||
|
||||
What is new in 1.8
|
||||
------------------
|
||||
|
||||
* Bug fixes
|
||||
|
||||
What is new in 1.7
|
||||
------------------
|
||||
|
||||
* Tolerate servers which print a banner on login
|
||||
|
||||
* Small improvements
|
||||
|
||||
What is new in 1.6
|
||||
------------------
|
||||
|
||||
* Workaround for missing truncate operation on old sftp servers
|
||||
|
||||
* Bug fixes
|
||||
|
||||
What is new in 1.5
|
||||
------------------
|
||||
|
||||
* Improvements to read performance. Now both read and write
|
||||
throughput should be very close to 'scp'
|
||||
|
||||
* If used with FUSE 2.6.0 or later, then perform better data caching.
|
||||
This should show dramatic speed improvements when a file is opened
|
||||
more than once
|
||||
|
||||
* Bug fixes
|
||||
|
||||
What is new in 1.4
|
||||
------------------
|
||||
|
||||
* Updated to version 25 of libfuse API
|
||||
|
||||
* This means that the 'cp' of readonly file to sshfs bug is finally
|
||||
solved (as long as using libfuse 2.5.0 or later *and* Linux 2.6.15
|
||||
or later)
|
||||
|
||||
* Sshfs now works on FreeBSD
|
||||
|
||||
* Added option to "transform" absolute symbolic links
|
||||
|
||||
What is new in 1.3
|
||||
------------------
|
||||
|
||||
* Add workaround for failure to rename to an existing file
|
||||
|
||||
* Simple user ID mapping
|
||||
|
||||
* Estimate disk usage of files based on size
|
||||
|
||||
* Report "infinite" disk space
|
||||
|
||||
* Bug fixes
|
||||
|
||||
What is new in 1.2
|
||||
------------------
|
||||
|
||||
* Better compatibility with different sftp servers
|
||||
|
||||
* Automatic reconnect (optional)
|
||||
|
||||
What is new in 1.1
|
||||
------------------
|
||||
|
||||
* Performance improvements:
|
||||
|
||||
- directory content caching
|
||||
|
||||
- symlink caching
|
||||
|
||||
- asynchronous writeback
|
||||
|
||||
- readahead
|
||||
|
||||
* Fixed '-p' option
|
||||
|
||||
What is new in 1.0
|
||||
------------------
|
||||
|
||||
* Initial release
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
Release 3.7.5 (2025-11-11)
|
||||
--------------------------
|
||||
|
||||
* New maintainers added
|
||||
* Main documentation updates to using Markdown
|
||||
* Updated Windows and macOS support
|
||||
* Added IPv6 support in -o directport
|
||||
* Added -o vsock option
|
||||
* Minor bugfixes
|
||||
|
||||
Release 3.7.3 (2022-05-26)
|
||||
--------------------------
|
||||
|
||||
* Minor bugfixes.
|
||||
|
||||
* This is the last release from the current maintainer. SSHFS is now no longer maintained
|
||||
or developed. Github issue tracking and pull requests have therefore been disabled. The
|
||||
mailing list (see below) is still available for use.
|
||||
|
||||
If you would like to take over this project, you are welcome to do so. Please fork it
|
||||
and develop the fork for a while. Once there has been 6 months of reasonable activity,
|
||||
please contact Nikolaus@rath.org and I'll be happy to give you ownership of this
|
||||
repository or replace with a pointer to the fork.
|
||||
|
||||
|
||||
Release 3.7.2 (2021-06-08)
|
||||
--------------------------
|
||||
|
||||
* Added a secondary check so if a mkdir request fails with EPERM an access request will be
|
||||
tried - returning EEXIST if the access was successful.
|
||||
Fixes: https://github.com/libfuse/sshfs/issues/243
|
||||
|
||||
|
||||
Release 3.7.1 (2020-11-09)
|
||||
--------------------------
|
||||
|
||||
* Minor bugfixes.
|
||||
|
||||
|
||||
Release 3.7.0 (2020-01-03)
|
||||
--------------------------
|
||||
|
||||
* New max_conns option enables the use of multiple connections to improve responsiveness
|
||||
during large file transfers. Thanks to Timo Savola for doing most of the implementation
|
||||
work, and thanks to CEA.fr for sponsoring remaining bugfixes and cleanups!
|
||||
|
||||
* The `buflimit` workaround is now disabled by default. The corresponding bug in OpenSSH
|
||||
has been fixed in 2007
|
||||
(cf. https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=365541#37), so this shouldn't be
|
||||
needed anymore. If you depend on this workaround, please let the SSHFS maintainers know,
|
||||
otherwise support for the workaround will be removed completely in a future version.
|
||||
|
||||
|
||||
Release 3.6.0 (2019-11-03)
|
||||
--------------------------
|
||||
|
||||
* Added "-o direct_io" option.
|
||||
This option disables the use of page cache in kernel.
|
||||
This is useful for example if the file size is not known before reading it.
|
||||
For example if you mount /proc dir from a remote host without the direct_io
|
||||
option, the read always will return zero bytes instead of actual data.
|
||||
* Added --verbose option.
|
||||
* Fixed a number of compiler warnings.
|
||||
* Improved performance under OS X.
|
||||
|
||||
|
||||
Release 3.5.2 (2019-04-13)
|
||||
--------------------------
|
||||
|
||||
* Fixed "-o idmap=user" to map both UID and GID on all OSs.
|
||||
* Fixed improper handling of sequential spaces spaces in "ssh_command" option
|
||||
|
||||
Release 3.5.1 (2018-12-22)
|
||||
--------------------------
|
||||
|
||||
* Documentation updates
|
||||
* Build system updates
|
||||
* Added "BindInterface" as valid "-o" option.
|
||||
|
||||
Release 3.5.0 (2018-08-28)
|
||||
--------------------------
|
||||
|
||||
* Fixed error code returned by rename(), allowing proper fallback.
|
||||
* Port to Cygwin.
|
||||
|
||||
Release 3.4.0 (2018-06-29)
|
||||
--------------------------
|
||||
|
||||
* Make utimens(NULL) result in timestamp "now" -- no more touched files
|
||||
dated 1970-01-01
|
||||
* New `createmode` workaround.
|
||||
* Fix `fstat` workaround regression.
|
||||
|
||||
Release 3.3.2 (2018-04-29)
|
||||
--------------------------
|
||||
|
||||
* New `renamexdev` workaround.
|
||||
|
||||
Release 3.3.1 (2017-10-25)
|
||||
--------------------------
|
||||
|
||||
* Manpage is now installed in correct directory.
|
||||
* SSHFS now supports (or rather: ignores) some options that it may
|
||||
receive as result of being mounted from ``/etc/mtab``. This includes
|
||||
things like ``user``, ``netdev``, or ``auto``.
|
||||
|
||||
SSHFS 3.3.0 (2017-09-20)
|
||||
------------------------
|
||||
|
||||
* Dropped support for writeback caching (and, as a consequence,
|
||||
"unreliable append" operation). As of kernel 4.14, the FUSE module's
|
||||
writeback implementation is not compatible with network filesystems
|
||||
and there are no imminent plans to change that.
|
||||
* Add support for mounting from /etc/fstab
|
||||
* Dropped support for building with autotools.
|
||||
* Added missing options to man page.
|
||||
|
||||
Release 3.2.0 (2017-08-06)
|
||||
--------------------------
|
||||
|
||||
* Re-enabled writeback cache.
|
||||
* SSHFS now supports O_APPEND.
|
||||
|
||||
Release 3.1.0 (2017-08-04)
|
||||
--------------------------
|
||||
|
||||
* Temporarily disabled the writeback cache feature, since there
|
||||
have been reports of dataloss when appending to files when
|
||||
writeback caching is enabled.
|
||||
|
||||
* Fixed a crash due to a race condition when listing
|
||||
directory contents.
|
||||
|
||||
* For improved backwards compatibility, SSHFS now also silently
|
||||
accepts the old ``-o cache_*`` options.
|
||||
|
||||
Release 3.0.0 (2017-07-08)
|
||||
--------------------------
|
||||
|
||||
* sshfs now requires libfuse 3.1.0 or newer.
|
||||
* When supported by the kernel, sshfs now uses writeback caching.
|
||||
* The `cache` option has been renamed to `dir_cache` for clarity.
|
||||
* Added unit tests
|
||||
* --debug now behaves like -o debug_sshfs, i.e. it enables sshfs
|
||||
debugging messages rather than libfuse debugging messages.
|
||||
* Documented limited hardlink support.
|
||||
* Added support for building with Meson.
|
||||
* Added support for more SSH options.
|
||||
* Dropped support for the *nodelay* workaround - the last OpenSSH
|
||||
version for which this was useful was released in 2006.
|
||||
* Dropped support for the *nodelaysrv* workaround. The same effect
|
||||
(enabling NODELAY on the server side *and* enabling X11 forwarding)
|
||||
can be achieved by explicitly passing `-o ForwardX11`
|
||||
* Removed support for `-o workaround=all`. Workarounds should always
|
||||
enabled explicitly and only when needed. There is no point in always
|
||||
enabling a potentially changing set of workarounds.
|
||||
|
||||
Release 2.9 (2017-04-17)
|
||||
------------------------
|
||||
|
||||
* Improved support for Cygwin.
|
||||
* Various small bugfixes.
|
||||
|
||||
Release 2.8 (2016-06-22)
|
||||
------------------------
|
||||
|
||||
* Added support for the "fsync" extension.
|
||||
* Fixed a build problem with bitbake
|
||||
|
||||
Release 2.7 (2016-03-01)
|
||||
------------------------
|
||||
|
||||
* Integrated osxfuse's copy of sshfs, which means that sshfs now works
|
||||
on OS X out of the box.
|
||||
* Added -o cache_max_size=N option to let users tune the maximum size of
|
||||
the cache in number of entries.
|
||||
* Added -o cache_clean_interval=N and -o cache_min_clean_interval=N
|
||||
options to let users tune the cleaning behavior of the cache.
|
||||
|
||||
Release 2.6 (2015-01-28)
|
||||
------------------------
|
||||
|
||||
* New maintainer (Nikolaus Rath <Nikolaus@rath.org>)
|
||||
|
||||
Release 2.5 (2014-01-14)
|
||||
------------------------
|
||||
|
||||
* Some performance improvements for large directories.
|
||||
* New `disable_hardlink` option.
|
||||
* Various small bugfixes.
|
||||
|
||||
Release 2.4 (2012-03-08)
|
||||
------------------------
|
||||
|
||||
* New `slave` option.
|
||||
* New `idmap`, `uidmap` and `gidmap` options.
|
||||
* Various small bugfixes.
|
||||
|
||||
Release 2.3 (2011-07-01)
|
||||
------------------------
|
||||
|
||||
* Support hard link creation if server is OpenSSH 5.7 or later
|
||||
* Small improvements and bug fixes
|
||||
* Check mount point and options before connecting to ssh server
|
||||
* New 'delay_connect' option
|
||||
|
||||
Release 2.2 (2008-10-20)
|
||||
------------------------
|
||||
|
||||
* Handle numerical IPv6 addresses enclosed in square brackets
|
||||
* Handle commas in usernames
|
||||
|
||||
Release 2.1 (2008-07-11)
|
||||
------------------------
|
||||
|
||||
* Small improvements and bug fixes
|
||||
|
||||
Release 2.0 (2008-04-23)
|
||||
------------------------
|
||||
|
||||
* Support password authentication with pam_mount
|
||||
|
||||
* Support atomic renames if server is OpenSSH 4.9 or later
|
||||
|
||||
* Support getting disk usage if server is OpenSSH 5.1 or later
|
||||
|
||||
* Small enhancements and bug fixes
|
||||
|
||||
What is new in 1.9
|
||||
------------------
|
||||
|
||||
* Fix a serious bug, that could result in sshfs hanging, crashing, or
|
||||
reporting out-of-memory
|
||||
|
||||
What is new in 1.8
|
||||
------------------
|
||||
|
||||
* Bug fixes
|
||||
|
||||
What is new in 1.7
|
||||
------------------
|
||||
|
||||
* Tolerate servers which print a banner on login
|
||||
|
||||
* Small improvements
|
||||
|
||||
What is new in 1.6
|
||||
------------------
|
||||
|
||||
* Workaround for missing truncate operation on old sftp servers
|
||||
|
||||
* Bug fixes
|
||||
|
||||
What is new in 1.5
|
||||
------------------
|
||||
|
||||
* Improvements to read performance. Now both read and write
|
||||
throughput should be very close to 'scp'
|
||||
|
||||
* If used with FUSE 2.6.0 or later, then perform better data caching.
|
||||
This should show dramatic speed improvements when a file is opened
|
||||
more than once
|
||||
|
||||
* Bug fixes
|
||||
|
||||
What is new in 1.4
|
||||
------------------
|
||||
|
||||
* Updated to version 25 of libfuse API
|
||||
|
||||
* This means that the 'cp' of readonly file to sshfs bug is finally
|
||||
solved (as long as using libfuse 2.5.0 or later *and* Linux 2.6.15
|
||||
or later)
|
||||
|
||||
* Sshfs now works on FreeBSD
|
||||
|
||||
* Added option to "transform" absolute symbolic links
|
||||
|
||||
What is new in 1.3
|
||||
------------------
|
||||
|
||||
* Add workaround for failure to rename to an existing file
|
||||
|
||||
* Simple user ID mapping
|
||||
|
||||
* Estimate disk usage of files based on size
|
||||
|
||||
* Report "infinite" disk space
|
||||
|
||||
* Bug fixes
|
||||
|
||||
What is new in 1.2
|
||||
------------------
|
||||
|
||||
* Better compatibility with different sftp servers
|
||||
|
||||
* Automatic reconnect (optional)
|
||||
|
||||
What is new in 1.1
|
||||
------------------
|
||||
|
||||
* Performance improvements:
|
||||
|
||||
- directory content caching
|
||||
|
||||
- symlink caching
|
||||
|
||||
- asynchronous writeback
|
||||
|
||||
- readahead
|
||||
|
||||
* Fixed '-p' option
|
||||
|
||||
What is new in 1.0
|
||||
------------------
|
||||
|
||||
* Initial release
|
||||
@@ -1,263 +0,0 @@
|
||||
SSHFS FAQ
|
||||
=========
|
||||
|
||||
1. I've found a bug and there's no solution in this FAQ, what
|
||||
should I do?
|
||||
2. Advantage of sshfs over NFS and Samba
|
||||
3. Create the device node
|
||||
4. mv fails with "Operation not permitted"
|
||||
5. cvs fails with "cvs [status aborted]: cannot get working
|
||||
directory: No such file or directory" in a sshfs mounted directory
|
||||
6. Changes on the server are not immediately visible in the
|
||||
mounted directory.
|
||||
7. Configuring the ssh connection
|
||||
8. What are the no_readahead and sshfs_sync options for?
|
||||
9. Why does df return strange values on partitions mounted via
|
||||
sshfs?
|
||||
10. How do I specify the remote mount point (since the example
|
||||
defaults to the home directory)
|
||||
11. sshfs hangs after a while
|
||||
12. Following symlinks on the server side
|
||||
13. Making absolute symlinks work
|
||||
14. Mounting as root
|
||||
15. Exporting via NFS
|
||||
16. Automatical mounting using /etc/fstab
|
||||
17. Why does SVN (etc...) fail with permission denied?
|
||||
18. Why does SVN (etc...) fail to rename files?
|
||||
19. Is there some neat way to do it in reverse?
|
||||
20. Might it be reasonable to disallow loops?
|
||||
21. How to mount through an intermediary ssh server, eg:
|
||||
localhost -> A -> B; mount B from localhost
|
||||
22. Alternative Solution
|
||||
23. I seem to have successfully mounted a remote directory, but
|
||||
performing an `ls -l` on the directory above the mount point shows
|
||||
the mount point's attributes as `? ? ? ? ? ?`. Nothing shows up in
|
||||
the directory either. What am I doing wrong?
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
1. I've found a bug and there's no solution in this FAQ, what should I do?
|
||||
|
||||
Please report the bug in the Github issue tracker:
|
||||
https://github.com/libfuse/sshfs/issues
|
||||
|
||||
Also logs with debugging output can be useful for diagnosing the
|
||||
problem. Try running sshfs with the following options:
|
||||
sshfs -odebug,sshfs_debug,loglevel=debug ...
|
||||
|
||||
Doing strace on the application which fails may also sometimes help:
|
||||
strace -f -o /tmp/strace application args ...
|
||||
|
||||
Note that large messages (over 40k) will be rejected from the mailing
|
||||
list. So try to keep the logs as short as possible.
|
||||
|
||||
|
||||
2. Advantage of sshfs over NFS and Samba
|
||||
|
||||
Users can mount remote resources they already have ssh access to,
|
||||
without requiring the remote machine to export the resource.
|
||||
|
||||
The remote resource can be mounted when it is needed in a location
|
||||
that is convenient for the user at that time, without needing to rely
|
||||
on a central, root-controlled file system table.
|
||||
|
||||
Automatic mounting, if desired, can be added to a shell script such as
|
||||
.bashrc (provided authentication is done using RSA/DSA keys).
|
||||
|
||||
Resources can be mounted over slow and unreliable (distant)
|
||||
connections.
|
||||
|
||||
3. Create the device node
|
||||
|
||||
If you don't use udev, you may get this error message:
|
||||
|
||||
fusermount: failed to open /dev/fuse: No such device or address
|
||||
|
||||
Before loading the fuse kernel module, create the device node
|
||||
manually:
|
||||
|
||||
mknod -m 666 /dev/fuse c 10 229
|
||||
|
||||
4. mv fails with "Operation not permitted".
|
||||
|
||||
Use -o workaround=rename (requires sshfs version >= 1.3).
|
||||
|
||||
5. cvs fails with "cvs status aborted?: cannot get working directory: No such
|
||||
file or directory" in a sshfs mounted directory
|
||||
|
||||
Use the -oreaddir_ino option. Example:
|
||||
sshfs -oreaddir_ino hostname:remote_dir mount_point
|
||||
|
||||
6. Changes on the server are not immediately visible in the mounted directory.
|
||||
|
||||
By default, sshfs caches things for 20 seconds, use -o cache_timeout=N
|
||||
to change the default cache timeout (in seconds) or -o cache=no for
|
||||
disabling the cache.
|
||||
|
||||
You can also control cache timeouts for directory listing etc with
|
||||
-o cache_stat_timeout=N,
|
||||
-o cache_dir_timout=N, and
|
||||
-o cache_link_timout=N.
|
||||
|
||||
7. Configuring the ssh connection
|
||||
|
||||
In addition to flags like -C, -p, and -o SSHOPT...=, you may find it
|
||||
easier to edit your /.ssh/config file. You can add an entry with any
|
||||
customization you want, test it with ssh, and finally use it with
|
||||
sshfs. As a bonus, you get a short mnemonic for your configuration.
|
||||
|
||||
8. What are the no_readahead and sshfs_sync options for?
|
||||
|
||||
These disable read and write optimizations respectively. They don't
|
||||
really make sense unless you're doing something special.
|
||||
|
||||
9. Why does df return strange values on partitions mounted via sshfs?
|
||||
|
||||
Because the SFTP protocol doesn't have a statfs operation this is
|
||||
currently not possible to display proper usage on remote partition.
|
||||
|
||||
10. How do I specfy the remote mount point (since the example defaults to the
|
||||
home directory)
|
||||
|
||||
The example shows:
|
||||
sshfs hostname: mountpoint
|
||||
|
||||
To specify a remote mount point use:
|
||||
sshfs hostname:remotemountpoint mountpoint
|
||||
|
||||
This might be obvious to others, but I ended up looking up the
|
||||
interface to sftp to see if I could learn how to specify the remote
|
||||
mount point, then thought about the way that scp specifies the remote
|
||||
directory, and it worked.
|
||||
|
||||
11. sshfs hangs after a while
|
||||
|
||||
Mounting works fine, I can use the files in Mountpoint as good as any
|
||||
other files on my system, but after bit of time, changing nothing on
|
||||
the remote files sshfs crashes. This means, I can not cd into the
|
||||
Mountpoint (xterm hangs, nautilus hangs... every program trying to
|
||||
access the Mountpoint gets stuck, and won't return).
|
||||
|
||||
Solution: add
|
||||
ServerAliveInterval 15
|
||||
|
||||
in your .ssh/config (or use -o ServerAliveInterval=15 on the sshfs
|
||||
command line but I did not test that solution). This will force the
|
||||
ssh connection to stay alive even if you have no activity.
|
||||
|
||||
12. Following symlinks on the server side
|
||||
|
||||
The -o follow_symlinks option will enable this.
|
||||
|
||||
13. Making absolute symlinks work
|
||||
|
||||
Use the -o transform_symlinks option, which will transform absolute
|
||||
symlinks (ones which point somewhere inside the mount) into relative
|
||||
ones.
|
||||
|
||||
14. Mounting as root
|
||||
|
||||
Generally it's not possible to use an sshfs mount as a "real"
|
||||
filesystem shared between multiple users. Some of this functionality
|
||||
can be enabled with the -o allow_other and -o default_permissions
|
||||
options, but files will not be created with the correct ownership,
|
||||
etc...
|
||||
|
||||
15. Exporting via NFS
|
||||
|
||||
Use the userspace NFS daemon http://sourceforge.net/projects/unfs
|
||||
|
||||
16. Automatical mounting using /etc/fstab
|
||||
|
||||
A line in /etc/fstab has the following format:
|
||||
sshfs#USERNAME@REMOTE_HOST:REMOTE_PATH MOUNT_POINT fuse SSHFS_OPTIONS 0 0
|
||||
|
||||
eg.
|
||||
sshfs#guest@guest.login.com:data /mnt/guest fuse \
|
||||
uid=1003,gid=100,umask=0,allow_other 0 0
|
||||
|
||||
17. Why does SVN (etc...) fail with permission denied?
|
||||
|
||||
This is a bug that happens when an application creates a read-only
|
||||
file opened for writing (e.g. open("foo", O_WRONLY|O_CREAT, 0444))
|
||||
|
||||
It has been fixed in sshfs version 1.3, but also requires FUSE version
|
||||
>=2.5.X and Linux kernel version >=2.6.15.
|
||||
|
||||
18. Why does SVN (etc...) fail to rename files?
|
||||
|
||||
$ svn co svn://anonsvn.kde.org/home/kde/trunk/KDE/kdelibs
|
||||
svn: Can't move 'kdelibs/.svn/tmp/entries' to 'kdelibs/.svn/entries':
|
||||
Operation not permitted
|
||||
|
||||
The reason is that SFTP protocol version 3 (which is implemented by
|
||||
OpenSSH's sftp-server) defines the rename operation slightly
|
||||
differently than POSIX. The difference is that renaming to an existing
|
||||
file or directory will fail instead of atomically replacing the old
|
||||
file.
|
||||
|
||||
The -o workaround=rename option will try to emulate POSIX rename
|
||||
semantics, but it cannot guarantee atomicity. In most of the cases
|
||||
this doesn't matter, and things will work fine with this option.
|
||||
|
||||
19. Is there some neat way to do it in reverse?
|
||||
|
||||
You want to mount a USB thumb drive onto a file server that is rather
|
||||
remote.
|
||||
|
||||
Assuming this is difficult because the laptop with the thumb drive is
|
||||
sitting behind NAT, firewalls, etc. then you need to create a
|
||||
port-forward:
|
||||
client$ ssh -R 2222:localhost:22 server
|
||||
server$ sshfs -p 2222 localhost:/media/usb1 myusb1
|
||||
|
||||
(Now, is there is there a smarter way that does not involve port
|
||||
opening login permissions in an undesireable direction?)
|
||||
|
||||
20. Might it be reasonable to disallow loops?
|
||||
|
||||
sshfs localhost:/mnt /mnt
|
||||
|
||||
This seems to produce undesirable results. --JoshuaRodman
|
||||
|
||||
21. How to mount through an intermediary ssh server, eg: localhost -> A -> B;
|
||||
mount B from localhost
|
||||
|
||||
Start by mounting the folder you need that is on "a" to a folder on
|
||||
"b" then mount the new folder that is on "a" to a folder on localhost.
|
||||
|
||||
IE: (These are NOT real commands, but a sequence of steps.
|
||||
A mounts B:/home/x on /mnt/Bx
|
||||
localhost mounts A:/mnt/Bx on ~/mydir
|
||||
|
||||
22. Alternative Solution:
|
||||
|
||||
1) Create a shell script to wrap the tunneling of one ssh command over
|
||||
another,
|
||||
$ cat >Atunnel <<EOF
|
||||
#!/bin/bash
|
||||
ssh -q A ssh -q "$@"
|
||||
EOF
|
||||
$ chmod u+x Atunnel
|
||||
|
||||
N.B. make sure to put this somewhere on your path. 2) sshfs mount as
|
||||
normal but using this script as the ssh command.
|
||||
$ sshfs -o ssh_command='Atunnel' B: ~/mydir
|
||||
|
||||
23. I seem to have successfully mounted a remote directory, but performing an
|
||||
`ls -l` on the directory above the mount point shows the mount point's
|
||||
attributes as `? ? ? ? ? ?`. Nothing shows up in the directory either. What
|
||||
am I doing wrong?
|
||||
|
||||
You probably specified a remote path with the tilde (~) in it. This
|
||||
doesn't seem to work. Instead, specify an absolute remote path:
|
||||
sshfs username@remote.host:/home/username/whatever my/mount
|
||||
|
||||
What options do i use to make playing media files (music) over sshfs more
|
||||
efficient?
|
||||
|
||||
MacFUSE doesn't seem to let me move files from one directory to another. It
|
||||
first asks for my local user password (i.e. the password on my Macbook Pro)
|
||||
and then produces the error message "The operation cannot be completed
|
||||
because one or more required items cannot be found. (Error code -120).
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
## Process this file with automake to produce Makefile.in
|
||||
|
||||
bin_PROGRAMS = sshfs
|
||||
|
||||
sshfs_SOURCES = sshfs.c cache.c cache.h
|
||||
if FUSE_OPT_COMPAT
|
||||
sshfs_SOURCES += compat/fuse_opt.c compat/fuse_opt.h
|
||||
endif
|
||||
if DARWIN_COMPAT
|
||||
sshfs_SOURCES += compat/darwin_compat.c compat/darwin_compat.h
|
||||
endif
|
||||
|
||||
sshfs_LDADD = $(SSHFS_LIBS)
|
||||
sshfs_CFLAGS = $(SSHFS_CFLAGS)
|
||||
sshfs_CPPFLAGS = -D_REENTRANT -DFUSE_USE_VERSION=26 -DLIBDIR=\"$(libdir)\" \
|
||||
-DIDMAP_DEFAULT="\"$(IDMAP_DEFAULT)\""
|
||||
|
||||
EXTRA_DIST = sshfs.1.in meson.build
|
||||
CLEANFILES = sshfs.1 sshfs.1.tmp
|
||||
|
||||
dist_man_MANS = sshfs.1
|
||||
|
||||
sshfs.1: sshfs.1.in
|
||||
$(AM_V_GEN)sed \
|
||||
-e 's/[@]IDMAP_DEFAULT@/$(IDMAP_DEFAULT)/g' \
|
||||
-e 's/[@]UNMOUNT_COMMAND@/$(UNMOUNT_COMMAND)/g' \
|
||||
<$(srcdir)/sshfs.1.in >sshfs.1.tmp || exit 1; \
|
||||
mv sshfs.1.tmp sshfs.1
|
||||
@@ -1,28 +1,43 @@
|
||||
SSHFS
|
||||
=====
|
||||
|
||||
# SSHFS
|
||||
|
||||
|
||||
About
|
||||
-----
|
||||
## 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.
|
||||
|
||||
|
||||
How to use
|
||||
----------
|
||||
## 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
|
||||
```
|
||||
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).
|
||||
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
|
||||
@@ -30,87 +45,98 @@ Also many ssh options can be specified (see the manual pages for
|
||||
|
||||
To unmount the filesystem:
|
||||
|
||||
fusermount -u mountpoint
|
||||
```
|
||||
fusermount -u mountpoint
|
||||
```
|
||||
|
||||
On BSD and OS-X, to unmount the filesystem:
|
||||
On BSD and macOS, to unmount the filesystem:
|
||||
|
||||
umount mountpoint
|
||||
```
|
||||
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
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
First, download the latest SSHFS release from
|
||||
https://github.com/libfuse/sshfs/releases. On Linux and BSD, you will
|
||||
also need to have [libfuse](http://github.com/libfuse/libfuse)
|
||||
installed. On OS-X, you need [OSXFUSE](https://osxfuse.github.io/)
|
||||
instead. Finally, you need the
|
||||
[glib](https://developer.gnome.org/glib/stable/) development package
|
||||
(which should be available from your operating system's package
|
||||
manager).
|
||||
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 and Ninja:
|
||||
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:
|
||||
|
||||
$ md build; cd build
|
||||
$ meson ..
|
||||
$ ninja
|
||||
$ sudo ninja install
|
||||
```
|
||||
$ 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
|
||||
$ ninja # rebuild
|
||||
```
|
||||
$ 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
|
||||
|
||||
|
||||
Alternate Installation
|
||||
----------------------
|
||||
|
||||
If you are not able to use Meson and Ninja, please report this to the
|
||||
sshfs mailing list. Until the problem is resolved, you may fall back
|
||||
to an in-source build using autotools:
|
||||
|
||||
$ ./configure
|
||||
$ make
|
||||
$ sudo make install
|
||||
|
||||
Note that support for building with autotools may disappear at some
|
||||
point, so if you depend on using autotools for some reason please let
|
||||
the sshfs developers know!
|
||||
|
||||
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
|
||||
|
||||
Caveats
|
||||
-------
|
||||
|
||||
Some SSH servers do not support atomically overwriting the destination
|
||||
when renaming a file. In this case you will get an error when you
|
||||
attempt to rename a file and the destination already exists. A
|
||||
workaround is to first remove the destination file, and then do the
|
||||
rename. SSHFS can do this automatically if you call it with `-o
|
||||
workaround=rename`. However, in this case it is still possible that
|
||||
someone (or something) recreates the destination file after SSHFS has
|
||||
removed it, but before SSHFS had the time to rename the old file. In
|
||||
this case, the rename will still fail.
|
||||
|
||||
|
||||
Getting Help
|
||||
------------
|
||||
|
||||
If you need help, please ask on the [SSHFS mailing
|
||||
list](http://groups.google.com/group/sshfs). To post to the list,
|
||||
please don't use the web interface but send an email to
|
||||
<sshfs@googlegroups.com>.
|
||||
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.
|
||||
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>
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
|
||||
#include "cache.h"
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <glib.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define DEFAULT_CACHE_TIMEOUT_SECS 20
|
||||
#define DEFAULT_MAX_CACHE_SIZE 10000
|
||||
@@ -27,7 +29,7 @@ struct cache {
|
||||
unsigned int max_size;
|
||||
unsigned int clean_interval_secs;
|
||||
unsigned int min_clean_interval_secs;
|
||||
struct fuse_cache_operations *next_oper;
|
||||
struct fuse_operations *next_oper;
|
||||
GHashTable *table;
|
||||
pthread_mutex_t lock;
|
||||
time_t last_cleaned;
|
||||
@@ -39,28 +41,51 @@ 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;
|
||||
time_t valid;
|
||||
};
|
||||
|
||||
struct fuse_cache_dirhandle {
|
||||
struct readdir_handle {
|
||||
const char *path;
|
||||
fuse_dirh_t h;
|
||||
fuse_dirfil_t filler;
|
||||
void *buf;
|
||||
fuse_fill_dir_t filler;
|
||||
GPtrArray *dir;
|
||||
uint64_t wrctr;
|
||||
};
|
||||
|
||||
struct file_handle {
|
||||
/* Did we send an open request to the underlying fs? */
|
||||
int is_open;
|
||||
|
||||
/* If so, this will hold its handle */
|
||||
unsigned long fs_fh;
|
||||
};
|
||||
|
||||
struct cache_dirent {
|
||||
char *name;
|
||||
struct stat stat;
|
||||
};
|
||||
|
||||
static void free_node(gpointer node_)
|
||||
{
|
||||
struct node *node = (struct node *) node_;
|
||||
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_;
|
||||
@@ -108,15 +133,12 @@ static void cache_purge_parent(const char *path)
|
||||
|
||||
void cache_invalidate(const char *path)
|
||||
{
|
||||
if (!cache.on)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
cache_purge(path);
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
}
|
||||
|
||||
void cache_invalidate_write(const char *path)
|
||||
static void cache_invalidate_write(const char *path)
|
||||
{
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
cache_purge(path);
|
||||
@@ -168,9 +190,6 @@ void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr)
|
||||
{
|
||||
struct node *node;
|
||||
|
||||
if (!cache.on)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
if (wrctr == cache.write_ctr) {
|
||||
node = cache_get(path);
|
||||
@@ -183,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)
|
||||
@@ -248,12 +269,25 @@ uint64_t cache_get_write_ctr(void)
|
||||
return res;
|
||||
}
|
||||
|
||||
static int cache_getattr(const char *path, struct stat *stbuf)
|
||||
static void *cache_init(struct fuse_conn_info *conn,
|
||||
struct fuse_config *cfg)
|
||||
{
|
||||
void *res;
|
||||
res = cache.next_oper->init(conn, cfg);
|
||||
|
||||
// Cache requires a path for each request
|
||||
cfg->nullpath_ok = 0;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int cache_getattr(const char *path, struct stat *stbuf,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
int err = cache_get_attr(path, stbuf);
|
||||
if (err) {
|
||||
uint64_t wrctr = cache_get_write_ctr();
|
||||
err = cache.next_oper->oper.getattr(path, stbuf);
|
||||
err = cache.next_oper->getattr(path, stbuf, fi);
|
||||
if (!err)
|
||||
cache_add_attr(path, stbuf, wrctr);
|
||||
}
|
||||
@@ -277,19 +311,58 @@ static int cache_readlink(const char *path, char *buf, size_t size)
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
err = cache.next_oper->oper.readlink(path, buf, size);
|
||||
err = cache.next_oper->readlink(path, buf, size);
|
||||
if (!err)
|
||||
cache_add_link(path, buf, size);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_dirfill(fuse_cache_dirh_t ch, const char *name,
|
||||
const struct stat *stbuf)
|
||||
|
||||
static int cache_opendir(const char *path, struct fuse_file_info *fi)
|
||||
{
|
||||
int err = ch->filler(ch->h, name, 0, 0);
|
||||
(void) path;
|
||||
struct file_handle *cfi;
|
||||
|
||||
cfi = malloc(sizeof(struct file_handle));
|
||||
if(cfi == NULL)
|
||||
return -ENOMEM;
|
||||
cfi->is_open = 0;
|
||||
fi->fh = (unsigned long) cfi;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cache_releasedir(const char *path, struct fuse_file_info *fi)
|
||||
{
|
||||
int err;
|
||||
struct file_handle *cfi;
|
||||
|
||||
cfi = (struct file_handle*) fi->fh;
|
||||
|
||||
if(cfi->is_open) {
|
||||
fi->fh = cfi->fs_fh;
|
||||
err = cache.next_oper->releasedir(path, fi);
|
||||
} else
|
||||
err = 0;
|
||||
|
||||
free(cfi);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_dirfill (void *buf, const char *name,
|
||||
const struct stat *stbuf, off_t off,
|
||||
enum fuse_fill_dir_flags flags)
|
||||
{
|
||||
int err;
|
||||
struct readdir_handle *ch;
|
||||
|
||||
ch = (struct readdir_handle*) buf;
|
||||
err = ch->filler(ch->buf, name, stbuf, off, flags);
|
||||
if (!err) {
|
||||
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;
|
||||
@@ -302,61 +375,67 @@ static int cache_dirfill(fuse_cache_dirh_t ch, const char *name,
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler)
|
||||
static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
|
||||
off_t offset, struct fuse_file_info *fi,
|
||||
enum fuse_readdir_flags flags)
|
||||
{
|
||||
struct fuse_cache_dirhandle ch;
|
||||
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++)
|
||||
filler(h, *dir, 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;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
|
||||
cfi = (struct file_handle*) fi->fh;
|
||||
if(cfi->is_open)
|
||||
fi->fh = cfi->fs_fh;
|
||||
else {
|
||||
if(cache.next_oper->opendir) {
|
||||
err = cache.next_oper->opendir(path, fi);
|
||||
if(err)
|
||||
return err;
|
||||
}
|
||||
cfi->is_open = 1;
|
||||
cfi->fs_fh = fi->fh;
|
||||
}
|
||||
|
||||
ch.path = path;
|
||||
ch.h = h;
|
||||
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->cache_getdir(path, &ch, cache_dirfill);
|
||||
err = cache.next_oper->readdir(path, &ch, cache_dirfill, offset, fi, flags);
|
||||
g_ptr_array_add(ch.dir, NULL);
|
||||
dir = (char **) ch.dir->pdata;
|
||||
if (!err)
|
||||
dir = ch.dir;
|
||||
if (!err) {
|
||||
cache_add_dir(path, dir);
|
||||
else
|
||||
g_strfreev(dir);
|
||||
g_ptr_array_free(ch.dir, FALSE);
|
||||
} else {
|
||||
g_ptr_array_free(dir, TRUE);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_unity_dirfill(fuse_cache_dirh_t ch, const char *name,
|
||||
const struct stat *stbuf)
|
||||
{
|
||||
(void) stbuf;
|
||||
return ch->filler(ch->h, name, 0, 0);
|
||||
}
|
||||
|
||||
static int cache_unity_getdir(const char *path, fuse_dirh_t h,
|
||||
fuse_dirfil_t filler)
|
||||
{
|
||||
struct fuse_cache_dirhandle ch;
|
||||
ch.h = h;
|
||||
ch.filler = filler;
|
||||
return cache.next_oper->cache_getdir(path, &ch, cache_unity_dirfill);
|
||||
}
|
||||
|
||||
static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
|
||||
{
|
||||
int err = cache.next_oper->oper.mknod(path, mode, rdev);
|
||||
int err = cache.next_oper->mknod(path, mode, rdev);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
@@ -364,7 +443,7 @@ static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
|
||||
|
||||
static int cache_mkdir(const char *path, mode_t mode)
|
||||
{
|
||||
int err = cache.next_oper->oper.mkdir(path, mode);
|
||||
int err = cache.next_oper->mkdir(path, mode);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
@@ -372,7 +451,7 @@ static int cache_mkdir(const char *path, mode_t mode)
|
||||
|
||||
static int cache_unlink(const char *path)
|
||||
{
|
||||
int err = cache.next_oper->oper.unlink(path);
|
||||
int err = cache.next_oper->unlink(path);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
@@ -380,7 +459,7 @@ static int cache_unlink(const char *path)
|
||||
|
||||
static int cache_rmdir(const char *path)
|
||||
{
|
||||
int err = cache.next_oper->oper.rmdir(path);
|
||||
int err = cache.next_oper->rmdir(path);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
@@ -388,15 +467,15 @@ static int cache_rmdir(const char *path)
|
||||
|
||||
static int cache_symlink(const char *from, const char *to)
|
||||
{
|
||||
int err = cache.next_oper->oper.symlink(from, to);
|
||||
int err = cache.next_oper->symlink(from, to);
|
||||
if (!err)
|
||||
cache_invalidate_dir(to);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_rename(const char *from, const char *to)
|
||||
static int cache_rename(const char *from, const char *to, unsigned int flags)
|
||||
{
|
||||
int err = cache.next_oper->oper.rename(from, to);
|
||||
int err = cache.next_oper->rename(from, to, flags);
|
||||
if (!err)
|
||||
cache_do_rename(from, to);
|
||||
return err;
|
||||
@@ -404,7 +483,7 @@ static int cache_rename(const char *from, const char *to)
|
||||
|
||||
static int cache_link(const char *from, const char *to)
|
||||
{
|
||||
int err = cache.next_oper->oper.link(from, to);
|
||||
int err = cache.next_oper->link(from, to);
|
||||
if (!err) {
|
||||
cache_invalidate(from);
|
||||
cache_invalidate_dir(to);
|
||||
@@ -412,33 +491,28 @@ static int cache_link(const char *from, const char *to)
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_chmod(const char *path, mode_t mode)
|
||||
static int cache_chmod(const char *path, mode_t mode,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
int err = cache.next_oper->oper.chmod(path, mode);
|
||||
int err = cache.next_oper->chmod(path, mode, fi);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_chown(const char *path, uid_t uid, gid_t gid)
|
||||
static int cache_chown(const char *path, uid_t uid, gid_t gid,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
int err = cache.next_oper->oper.chown(path, uid, gid);
|
||||
int err = cache.next_oper->chown(path, uid, gid, fi);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_truncate(const char *path, off_t size)
|
||||
static int cache_utimens(const char *path, const struct timespec tv[2],
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
int err = cache.next_oper->oper.truncate(path, size);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_utime(const char *path, struct utimbuf *buf)
|
||||
{
|
||||
int err = cache.next_oper->oper.utime(path, buf);
|
||||
int err = cache.next_oper->utimens(path, tv, fi);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
@@ -447,140 +521,95 @@ static int cache_utime(const char *path, struct utimbuf *buf)
|
||||
static int cache_write(const char *path, const char *buf, size_t size,
|
||||
off_t offset, struct fuse_file_info *fi)
|
||||
{
|
||||
int res = cache.next_oper->oper.write(path, buf, size, offset, fi);
|
||||
int res = cache.next_oper->write(path, buf, size, offset, fi);
|
||||
if (res >= 0)
|
||||
cache_invalidate_write(path);
|
||||
return res;
|
||||
}
|
||||
|
||||
#if FUSE_VERSION >= 25
|
||||
static int cache_create(const char *path, mode_t mode,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
int err = cache.next_oper->oper.create(path, mode, fi);
|
||||
int err = cache.next_oper->create(path, mode, fi);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_ftruncate(const char *path, off_t size,
|
||||
struct fuse_file_info *fi)
|
||||
static int cache_truncate(const char *path, off_t size,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
int err = cache.next_oper->oper.ftruncate(path, size, fi);
|
||||
int err = cache.next_oper->truncate(path, size, fi);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_fgetattr(const char *path, struct stat *stbuf,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
int err = cache_get_attr(path, stbuf);
|
||||
if (err) {
|
||||
uint64_t wrctr = cache_get_write_ctr();
|
||||
err = cache.next_oper->oper.fgetattr(path, stbuf, fi);
|
||||
if (!err)
|
||||
cache_add_attr(path, stbuf, wrctr);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void cache_unity_fill(struct fuse_cache_operations *oper,
|
||||
struct fuse_operations *cache_oper)
|
||||
{
|
||||
#if FUSE_VERSION >= 23
|
||||
cache_oper->init = oper->oper.init;
|
||||
#endif
|
||||
cache_oper->getattr = oper->oper.getattr;
|
||||
cache_oper->access = oper->oper.access;
|
||||
cache_oper->readlink = oper->oper.readlink;
|
||||
cache_oper->getdir = cache_unity_getdir;
|
||||
cache_oper->mknod = oper->oper.mknod;
|
||||
cache_oper->mkdir = oper->oper.mkdir;
|
||||
cache_oper->symlink = oper->oper.symlink;
|
||||
cache_oper->unlink = oper->oper.unlink;
|
||||
cache_oper->rmdir = oper->oper.rmdir;
|
||||
cache_oper->rename = oper->oper.rename;
|
||||
cache_oper->link = oper->oper.link;
|
||||
cache_oper->chmod = oper->oper.chmod;
|
||||
cache_oper->chown = oper->oper.chown;
|
||||
cache_oper->truncate = oper->oper.truncate;
|
||||
cache_oper->utime = oper->oper.utime;
|
||||
cache_oper->open = oper->oper.open;
|
||||
cache_oper->read = oper->oper.read;
|
||||
cache_oper->write = oper->oper.write;
|
||||
cache_oper->flush = oper->oper.flush;
|
||||
cache_oper->release = oper->oper.release;
|
||||
cache_oper->fsync = oper->oper.fsync;
|
||||
cache_oper->statfs = oper->oper.statfs;
|
||||
cache_oper->setxattr = oper->oper.setxattr;
|
||||
cache_oper->getxattr = oper->oper.getxattr;
|
||||
cache_oper->listxattr = oper->oper.listxattr;
|
||||
cache_oper->removexattr = oper->oper.removexattr;
|
||||
#if FUSE_VERSION >= 25
|
||||
cache_oper->create = oper->oper.create;
|
||||
cache_oper->ftruncate = oper->oper.ftruncate;
|
||||
cache_oper->fgetattr = oper->oper.fgetattr;
|
||||
#endif
|
||||
#if FUSE_VERSION >= 29
|
||||
cache_oper->flag_nullpath_ok = oper->oper.flag_nullpath_ok;
|
||||
cache_oper->flag_nopath = oper->oper.flag_nopath;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cache_fill(struct fuse_cache_operations *oper,
|
||||
static void cache_fill(struct fuse_operations *oper,
|
||||
struct fuse_operations *cache_oper)
|
||||
{
|
||||
cache_oper->getattr = oper->oper.getattr ? cache_getattr : NULL;
|
||||
cache_oper->readlink = oper->oper.readlink ? cache_readlink : NULL;
|
||||
cache_oper->getdir = oper->cache_getdir ? cache_getdir : NULL;
|
||||
cache_oper->mknod = oper->oper.mknod ? cache_mknod : NULL;
|
||||
cache_oper->mkdir = oper->oper.mkdir ? cache_mkdir : NULL;
|
||||
cache_oper->symlink = oper->oper.symlink ? cache_symlink : NULL;
|
||||
cache_oper->unlink = oper->oper.unlink ? cache_unlink : NULL;
|
||||
cache_oper->rmdir = oper->oper.rmdir ? cache_rmdir : NULL;
|
||||
cache_oper->rename = oper->oper.rename ? cache_rename : NULL;
|
||||
cache_oper->link = oper->oper.link ? cache_link : NULL;
|
||||
cache_oper->chmod = oper->oper.chmod ? cache_chmod : NULL;
|
||||
cache_oper->chown = oper->oper.chown ? cache_chown : NULL;
|
||||
cache_oper->truncate = oper->oper.truncate ? cache_truncate : NULL;
|
||||
cache_oper->utime = oper->oper.utime ? cache_utime : NULL;
|
||||
cache_oper->write = oper->oper.write ? cache_write : NULL;
|
||||
#if FUSE_VERSION >= 25
|
||||
cache_oper->create = oper->oper.create ? cache_create : NULL;
|
||||
cache_oper->ftruncate = oper->oper.ftruncate ? cache_ftruncate : NULL;
|
||||
cache_oper->fgetattr = oper->oper.fgetattr ? cache_fgetattr : NULL;
|
||||
#endif
|
||||
#if FUSE_VERSION >= 29
|
||||
cache_oper->flag_nullpath_ok = 0;
|
||||
cache_oper->flag_nopath = 0;
|
||||
#endif
|
||||
|
||||
cache_oper->access = oper->access;
|
||||
cache_oper->chmod = oper->chmod ? cache_chmod : NULL;
|
||||
cache_oper->chown = oper->chown ? cache_chown : NULL;
|
||||
cache_oper->create = oper->create ? cache_create : NULL;
|
||||
cache_oper->flush = oper->flush;
|
||||
cache_oper->fsync = oper->fsync;
|
||||
cache_oper->getattr = oper->getattr ? cache_getattr : NULL;
|
||||
cache_oper->getxattr = oper->getxattr;
|
||||
cache_oper->init = cache_init;
|
||||
cache_oper->link = oper->link ? cache_link : NULL;
|
||||
cache_oper->listxattr = oper->listxattr;
|
||||
cache_oper->mkdir = oper->mkdir ? cache_mkdir : NULL;
|
||||
cache_oper->mknod = oper->mknod ? cache_mknod : NULL;
|
||||
cache_oper->open = oper->open;
|
||||
cache_oper->opendir = cache_opendir;
|
||||
cache_oper->read = oper->read;
|
||||
cache_oper->readdir = oper->readdir ? cache_readdir : NULL;
|
||||
cache_oper->readlink = oper->readlink ? cache_readlink : NULL;
|
||||
cache_oper->release = oper->release;
|
||||
cache_oper->releasedir = cache_releasedir;
|
||||
cache_oper->removexattr = oper->removexattr;
|
||||
cache_oper->rename = oper->rename ? cache_rename : NULL;
|
||||
cache_oper->rmdir = oper->rmdir ? cache_rmdir : NULL;
|
||||
cache_oper->setxattr = oper->setxattr;
|
||||
cache_oper->statfs = oper->statfs;
|
||||
cache_oper->symlink = oper->symlink ? cache_symlink : NULL;
|
||||
cache_oper->truncate = oper->truncate ? cache_truncate : NULL;
|
||||
cache_oper->unlink = oper->unlink ? cache_unlink : NULL;
|
||||
cache_oper->utimens = oper->utimens ? cache_utimens : NULL;
|
||||
cache_oper->write = oper->write ? cache_write : NULL;
|
||||
}
|
||||
|
||||
struct fuse_operations *cache_init(struct fuse_cache_operations *oper)
|
||||
struct fuse_operations *cache_wrap(struct fuse_operations *oper)
|
||||
{
|
||||
static struct fuse_operations cache_oper;
|
||||
cache.next_oper = oper;
|
||||
|
||||
cache_unity_fill(oper, &cache_oper);
|
||||
if (cache.on) {
|
||||
cache_fill(oper, &cache_oper);
|
||||
pthread_mutex_init(&cache.lock, NULL);
|
||||
cache.table = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
g_free, free_node);
|
||||
if (cache.table == NULL) {
|
||||
fprintf(stderr, "failed to create cache\n");
|
||||
return NULL;
|
||||
}
|
||||
cache_fill(oper, &cache_oper);
|
||||
pthread_mutex_init(&cache.lock, NULL);
|
||||
cache.table = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
g_free, free_node);
|
||||
if (cache.table == NULL) {
|
||||
fprintf(stderr, "failed to create cache\n");
|
||||
return NULL;
|
||||
}
|
||||
return &cache_oper;
|
||||
}
|
||||
|
||||
static const struct fuse_opt cache_opts[] = {
|
||||
{ "cache=yes", offsetof(struct cache, on), 1 },
|
||||
{ "cache=no", offsetof(struct cache, on), 0 },
|
||||
{ "dcache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
|
||||
{ "dcache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
|
||||
{ "dcache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
|
||||
{ "dcache_stat_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
|
||||
{ "dcache_dir_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
|
||||
{ "dcache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
|
||||
{ "dcache_max_size=%u", offsetof(struct cache, max_size), 0 },
|
||||
{ "dcache_clean_interval=%u", offsetof(struct cache,
|
||||
clean_interval_secs), 0 },
|
||||
{ "dcache_min_clean_interval=%u", offsetof(struct cache,
|
||||
min_clean_interval_secs), 0 },
|
||||
|
||||
/* For backwards compatibility */
|
||||
{ "cache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
|
||||
{ "cache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
|
||||
{ "cache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
|
||||
@@ -589,9 +618,9 @@ static const struct fuse_opt cache_opts[] = {
|
||||
{ "cache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
|
||||
{ "cache_max_size=%u", offsetof(struct cache, max_size), 0 },
|
||||
{ "cache_clean_interval=%u", offsetof(struct cache,
|
||||
clean_interval_secs), 0 },
|
||||
clean_interval_secs), 0 },
|
||||
{ "cache_min_clean_interval=%u", offsetof(struct cache,
|
||||
min_clean_interval_secs), 0 },
|
||||
min_clean_interval_secs), 0 },
|
||||
FUSE_OPT_END
|
||||
};
|
||||
|
||||
@@ -603,7 +632,6 @@ int cache_parse_options(struct fuse_args *args)
|
||||
cache.max_size = DEFAULT_MAX_CACHE_SIZE;
|
||||
cache.clean_interval_secs = DEFAULT_CACHE_CLEAN_INTERVAL_SECS;
|
||||
cache.min_clean_interval_secs = DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS;
|
||||
cache.on = 1;
|
||||
|
||||
return fuse_opt_parse(args, &cache, cache_opts, NULL);
|
||||
}
|
||||
|
||||
@@ -9,20 +9,7 @@
|
||||
#include <fuse.h>
|
||||
#include <fuse_opt.h>
|
||||
|
||||
#ifndef FUSE_VERSION
|
||||
#define FUSE_VERSION (FUSE_MAJOR_VERSION * 10 + FUSE_MINOR_VERSION)
|
||||
#endif
|
||||
|
||||
typedef struct fuse_cache_dirhandle *fuse_cache_dirh_t;
|
||||
typedef int (*fuse_cache_dirfil_t) (fuse_cache_dirh_t h, const char *name,
|
||||
const struct stat *stbuf);
|
||||
|
||||
struct fuse_cache_operations {
|
||||
struct fuse_operations oper;
|
||||
int (*cache_getdir) (const char *, fuse_cache_dirh_t, fuse_cache_dirfil_t);
|
||||
};
|
||||
|
||||
struct fuse_operations *cache_init(struct fuse_cache_operations *oper);
|
||||
struct fuse_operations *cache_wrap(struct fuse_operations *oper);
|
||||
int cache_parse_options(struct fuse_args *args);
|
||||
void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr);
|
||||
void cache_invalidate(const char *path);
|
||||
|
||||
@@ -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_ */
|
||||
@@ -1,44 +0,0 @@
|
||||
AC_INIT(sshfs, 2.9)
|
||||
AC_CANONICAL_TARGET
|
||||
AM_INIT_AUTOMAKE([foreign subdir-objects])
|
||||
AM_CONFIG_HEADER(config.h)
|
||||
|
||||
AC_PROG_CC
|
||||
AM_PROG_CC_C_O
|
||||
CFLAGS="$CFLAGS -Wall -W"
|
||||
LIBS=
|
||||
AC_SEARCH_LIBS(dlsym, [dl])
|
||||
LIBS=
|
||||
|
||||
case "$target_os" in
|
||||
*linux*) osname=linux;;
|
||||
*darwin*) osname=darwin;;
|
||||
*) osname=unknown;;
|
||||
esac
|
||||
|
||||
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
|
||||
PKG_CHECK_MODULES([SSHFS], [fuse >= 2.3 glib-2.0 gthread-2.0])
|
||||
have_fuse_opt_parse=no
|
||||
oldlibs="$LIBS"
|
||||
LIBS="$LIBS $SSHFS_LIBS"
|
||||
AC_CHECK_FUNC([fuse_opt_parse], [have_fuse_opt_parse=yes])
|
||||
LIBS="$oldlibs"
|
||||
if test "$have_fuse_opt_parse" = no -o "$osname" = darwin; then
|
||||
CFLAGS="$CFLAGS -I${srcdir}/compat"
|
||||
fi
|
||||
AM_CONDITIONAL(FUSE_OPT_COMPAT, test "$have_fuse_opt_parse" = no)
|
||||
AM_CONDITIONAL(DARWIN_COMPAT, test "$osname" = darwin)
|
||||
|
||||
AC_CHECK_PROG(UNMOUNT_COMMAND, fusermount, fusermount -u, umount)
|
||||
|
||||
# TODO: Figure out why we special-case this in Darwin. Would be nice if
|
||||
# the default setting was consistent across platforms so we wouldn't need
|
||||
# to care about it here.
|
||||
case "$osname" in
|
||||
darwin) IDMAP_DEFAULT=user ;;
|
||||
*) IDMAP_DEFAULT=none ;;
|
||||
esac
|
||||
AC_SUBST(IDMAP_DEFAULT)
|
||||
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
AC_OUTPUT
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Create tarball from Git tag, removing and adding
|
||||
# some files.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
TAG="$(git tag --list 'sshfs-3*' --sort=-taggerdate | head -1)"
|
||||
else
|
||||
TAG="$1"
|
||||
fi
|
||||
|
||||
echo "Creating release tarball for ${TAG}..."
|
||||
|
||||
mkdir "${TAG}"
|
||||
git archive --format=tar "${TAG}" | tar -x "--directory=${TAG}"
|
||||
find "${TAG}" -name .gitignore -delete
|
||||
rm "${TAG}/make_release_tarball.sh" \
|
||||
"${TAG}/.travis.yml" \
|
||||
"${TAG}/test/travis-build.sh" \
|
||||
"${TAG}/test/travis-install.sh"
|
||||
tar -cJf "${TAG}.tar.xz" "${TAG}/"
|
||||
gpg --armor --detach-sign "${TAG}.tar.xz"
|
||||
|
||||
PREV_TAG="$(git tag --list 'sshfs-3*' --sort=-taggerdate --merged "${TAG}^"| head -1)"
|
||||
echo "Contributors from ${PREV_TAG} to ${TAG}:"
|
||||
git log --pretty="format:%an <%aE>" "${PREV_TAG}..${TAG}" | sort -u
|
||||
+23
-10
@@ -1,6 +1,6 @@
|
||||
project('sshfs', 'c', version: '3.0.0',
|
||||
meson_version: '>= 0.38',
|
||||
default_options: [ 'buildtype=plain' ])
|
||||
project('sshfs', 'c', version: '3.7.5',
|
||||
meson_version: '>= 0.40',
|
||||
default_options: [ 'buildtype=debugoptimized' ])
|
||||
|
||||
add_global_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H',
|
||||
'-Wall', '-Wextra', '-Wno-sign-compare',
|
||||
@@ -25,6 +25,8 @@ if not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ])
|
||||
endif
|
||||
|
||||
|
||||
rst2man = find_program('rst2man', 'rst2man.py', required: false)
|
||||
|
||||
cfg = configuration_data()
|
||||
|
||||
cfg.set_quoted('PACKAGE_VERSION', meson.project_version())
|
||||
@@ -32,28 +34,39 @@ 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')
|
||||
endif
|
||||
|
||||
configure_file(input: 'sshfs.1.in',
|
||||
output: 'sshfs.1',
|
||||
configuration : cfg)
|
||||
configure_file(output: 'config.h',
|
||||
configuration : cfg)
|
||||
|
||||
sshfs_deps = [ dependency('fuse', version: '>= 2.3'),
|
||||
sshfs_deps = [ dependency('fuse3', version: '>= 3.1.0'),
|
||||
dependency('glib-2.0'),
|
||||
dependency('gthread-2.0') ]
|
||||
|
||||
executable('sshfs', sshfs_sources,
|
||||
include_directories: include_dirs,
|
||||
dependencies: sshfs_deps,
|
||||
c_args: ['-DFUSE_USE_VERSION=26'],
|
||||
c_args: ['-DFUSE_USE_VERSION=31'],
|
||||
install: true,
|
||||
install_dir: get_option('bindir'))
|
||||
|
||||
install_man('sshfs.1')
|
||||
if rst2man.found()
|
||||
custom_target('manpages', input: [ 'sshfs.rst' ], output: [ 'sshfs.1' ],
|
||||
command: [rst2man, '@INPUT@', '@OUTPUT@'], install: true,
|
||||
install_dir: join_paths(get_option('mandir'), 'man1'))
|
||||
else
|
||||
message('rst2man not found, not building manual page.')
|
||||
endif
|
||||
|
||||
meson.add_install_script('utils/install_helper.sh',
|
||||
get_option('sbindir'),
|
||||
get_option('bindir'))
|
||||
|
||||
|
||||
subdir('test')
|
||||
|
||||
-284
@@ -1,284 +0,0 @@
|
||||
.TH SSHFS "1" "April 2008" "SSHFS version 2.0" "User Commands"
|
||||
.SH NAME
|
||||
SSHFS \- filesystem client based on ssh
|
||||
.SH SYNOPSIS
|
||||
.SS mounting
|
||||
.TP
|
||||
\fBsshfs\fP [\fIuser\fP@]\fBhost\fP:[\fIdir\fP] \fBmountpoint\fP [\fIoptions\fP]
|
||||
.SS unmounting
|
||||
.TP
|
||||
\fB@UNMOUNT_COMMAND@ mountpoint\fP
|
||||
.SH DESCRIPTION
|
||||
SSHFS (Secure SHell FileSystem) is a file system for Linux (and other
|
||||
operating systems with a FUSE implementation, such as Mac OS X or FreeBSD)
|
||||
capable of operating on files on a remote computer using just a secure
|
||||
shell login on the remote computer. On the local computer where the SSHFS
|
||||
is mounted, the implementation makes use of the FUSE (Filesystem in
|
||||
Userspace) kernel module. The practical effect of this is that the end user
|
||||
can seamlessly interact with remote files being securely served over SSH
|
||||
just as if they were local files on his/her computer. On the remote
|
||||
computer the SFTP subsystem of SSH is used.
|
||||
.PP
|
||||
If \fIhost\fP is a numeric IPv6 address, it needs to be enclosed in
|
||||
square brackets.
|
||||
.SH OPTIONS
|
||||
.SS "general options:"
|
||||
.TP
|
||||
\fB\-o\fR opt,[opt...]
|
||||
mount options
|
||||
.TP
|
||||
\fB\-h\fR \fB\-\-help\fR
|
||||
print help
|
||||
.TP
|
||||
\fB\-V\fR \fB\-\-version\fR
|
||||
print version
|
||||
.SS "SSHFS options:"
|
||||
.TP
|
||||
\fB\-p\fR PORT
|
||||
equivalent to '\-o port=PORT'
|
||||
.TP
|
||||
\fB\-C\fR
|
||||
equivalent to '\-o compression=yes'
|
||||
.TP
|
||||
\fB\-F\fR ssh_configfile
|
||||
specifies alternative ssh configuration file
|
||||
.TP
|
||||
\fB\-1\fR
|
||||
equivalent to '\-o ssh_protocol=1'
|
||||
.TP
|
||||
\fB\-o\fR reconnect
|
||||
reconnect to server
|
||||
.TP
|
||||
\fB\-o\fR delay_connect
|
||||
delay connection to server
|
||||
.TP
|
||||
\fB\-o\fR sshfs_sync
|
||||
synchronous writes
|
||||
.TP
|
||||
\fB\-o\fR no_readahead
|
||||
synchronous reads (no speculative readahead)
|
||||
.TP
|
||||
\fB\-o\fR sync_readdir
|
||||
synchronous readdir
|
||||
.TP
|
||||
\fB\-o\fR sshfs_debug
|
||||
print some debugging information
|
||||
.TP
|
||||
\fB\-o\fR cache=BOOL
|
||||
enable caching {yes,no} (default: yes)
|
||||
.TP
|
||||
\fB\-o\fR cache_timeout=N
|
||||
sets timeout for caches in seconds (default: 20)
|
||||
.TP
|
||||
\fB\-o\fR cache_X_timeout=N
|
||||
sets timeout for {stat,dir,link} cache
|
||||
.TP
|
||||
\fB\-o\fR workaround=LIST
|
||||
colon separated list of workarounds
|
||||
.RS 8
|
||||
.TP
|
||||
none
|
||||
no workarounds enabled
|
||||
.TP
|
||||
all
|
||||
all workarounds enabled
|
||||
.TP
|
||||
[no]rename
|
||||
fix renaming to existing file (default: off)
|
||||
.TP
|
||||
[no]nodelaysrv
|
||||
set nodelay tcp flag in ssh (default: off)
|
||||
.TP
|
||||
[no]truncate
|
||||
fix truncate for old servers (default: off)
|
||||
.TP
|
||||
[no]buflimit
|
||||
fix buffer fillup bug in server (default: on)
|
||||
.RE
|
||||
.TP
|
||||
\fB\-o\fR idmap=TYPE
|
||||
user/group ID mapping (default: @IDMAP_DEFAULT@)
|
||||
.RS 8
|
||||
.TP
|
||||
none
|
||||
no translation of the ID space
|
||||
.TP
|
||||
user
|
||||
only translate UID/GID of connecting user
|
||||
.TP
|
||||
file
|
||||
translate UIDs/GIDs based upon the contents of \fBuidfile \fR and
|
||||
\fBgidfile\fR
|
||||
.RE
|
||||
.TP
|
||||
\fB\-o\fR uidfile=FILE
|
||||
file containing username:uid mappings for \fBidmap=file\fR
|
||||
.RE
|
||||
.TP
|
||||
\fB\-o\fR gidfile=FILE
|
||||
file containing groupname:gid mappings for \fBidmap=file\fR
|
||||
.RE
|
||||
.TP
|
||||
\fB\-o\fR nomap=TYPE
|
||||
with idmap=file, how to handle missing mappings
|
||||
.RS 8
|
||||
.TP
|
||||
ignore
|
||||
don't do any re-mapping
|
||||
.TP
|
||||
error
|
||||
return an error (default)
|
||||
.RE
|
||||
.TP
|
||||
\fB\-o\fR ssh_command=CMD
|
||||
execute CMD instead of 'ssh'
|
||||
.TP
|
||||
\fB\-o\fR ssh_protocol=N
|
||||
ssh protocol to use (default: 2)
|
||||
.TP
|
||||
\fB\-o\fR sftp_server=SERV
|
||||
path to sftp server or subsystem (default: sftp)
|
||||
.TP
|
||||
\fB\-o\fR directport=PORT
|
||||
directly connect to PORT bypassing ssh
|
||||
.TP
|
||||
\fB\-o\fR slave
|
||||
communicate over stdin and stdout bypassing network
|
||||
.TP
|
||||
\fB\-o\fR disable_hardlink
|
||||
link(2) will return with errno set to ENOSYS. Hard links don't currently work
|
||||
perfectly on sshfs, and this confuses some programs. If that happens try
|
||||
disabling hard links with this option.
|
||||
.TP
|
||||
\fB\-o\fR transform_symlinks
|
||||
transform absolute symlinks to relative
|
||||
.TP
|
||||
\fB\-o\fR follow_symlinks
|
||||
follow symlinks on the server
|
||||
.TP
|
||||
\fB\-o\fR no_check_root
|
||||
don't check for existence of 'dir' on server
|
||||
.TP
|
||||
\fB\-o\fR password_stdin
|
||||
read password from stdin (only for pam_mount!)
|
||||
.TP
|
||||
\fB\-o\fR SSHOPT=VAL
|
||||
ssh options (see man ssh_config)
|
||||
.SS "FUSE options:"
|
||||
.TP
|
||||
\fB\-d\fR \fB\-o\fR debug
|
||||
enable debug output (implies \fB\-f\fR)
|
||||
.TP
|
||||
\fB\-f\fR
|
||||
foreground operation
|
||||
.TP
|
||||
\fB\-s\fR
|
||||
disable multi\-threaded operation
|
||||
.TP
|
||||
\fB\-o\fR allow_other
|
||||
allow access to other users
|
||||
.TP
|
||||
\fB\-o\fR allow_root
|
||||
allow access to root
|
||||
.TP
|
||||
\fB\-o\fR nonempty
|
||||
allow mounts over non\-empty file/dir
|
||||
.HP
|
||||
\fB\-o\fR default_permissions
|
||||
enable permission checking by kernel
|
||||
.TP
|
||||
\fB\-o\fR fsname=NAME
|
||||
set filesystem name
|
||||
.TP
|
||||
\fB\-o\fR subtype=NAME
|
||||
set filesystem type
|
||||
.TP
|
||||
\fB\-o\fR large_read
|
||||
issue large read requests (2.4 only)
|
||||
.TP
|
||||
\fB\-o\fR max_read=N
|
||||
set maximum size of read requests
|
||||
.TP
|
||||
\fB\-o\fR hard_remove
|
||||
immediate removal (don't hide files)
|
||||
.TP
|
||||
\fB\-o\fR use_ino
|
||||
let filesystem set inode numbers
|
||||
.TP
|
||||
\fB\-o\fR readdir_ino
|
||||
try to fill in d_ino in readdir
|
||||
.TP
|
||||
\fB\-o\fR direct_io
|
||||
use direct I/O
|
||||
.TP
|
||||
\fB\-o\fR kernel_cache
|
||||
cache files in kernel
|
||||
.TP
|
||||
\fB\-o\fR [no]auto_cache
|
||||
enable caching based on modification times
|
||||
.TP
|
||||
\fB\-o\fR umask=M
|
||||
set file permissions (octal)
|
||||
.TP
|
||||
\fB\-o\fR uid=N
|
||||
set file owner
|
||||
.TP
|
||||
\fB\-o\fR gid=N
|
||||
set file group
|
||||
.TP
|
||||
\fB\-o\fR entry_timeout=T
|
||||
cache timeout for names (1.0s)
|
||||
.TP
|
||||
\fB\-o\fR negative_timeout=T
|
||||
cache timeout for deleted names (0.0s)
|
||||
.TP
|
||||
\fB\-o\fR attr_timeout=T
|
||||
cache timeout for attributes (1.0s)
|
||||
.TP
|
||||
\fB\-o\fR ac_attr_timeout=T
|
||||
auto cache timeout for attributes (attr_timeout)
|
||||
.TP
|
||||
\fB\-o\fR intr
|
||||
allow requests to be interrupted
|
||||
.TP
|
||||
\fB\-o\fR intr_signal=NUM
|
||||
signal to send on interrupt (10)
|
||||
.TP
|
||||
\fB\-o\fR modules=M1[:M2...]
|
||||
names of modules to push onto filesystem stack
|
||||
.TP
|
||||
\fB\-o\fR max_write=N
|
||||
set maximum size of write requests
|
||||
.TP
|
||||
\fB\-o\fR max_readahead=N
|
||||
set maximum readahead
|
||||
.TP
|
||||
\fB\-o\fR async_read
|
||||
perform reads asynchronously (default)
|
||||
.TP
|
||||
\fB\-o\fR sync_read
|
||||
perform reads synchronously
|
||||
.SS "Module options:"
|
||||
.TP
|
||||
[subdir]
|
||||
.TP
|
||||
\fB\-o\fR subdir=DIR
|
||||
prepend this directory to all paths (mandatory)
|
||||
.TP
|
||||
\fB\-o\fR [no]rellinks
|
||||
transform absolute symlinks to relative
|
||||
.TP
|
||||
[iconv]
|
||||
.TP
|
||||
\fB\-o\fR from_code=CHARSET
|
||||
original encoding of file names (default: UTF-8)
|
||||
.TP
|
||||
\fB\-o\fR to_code=CHARSET
|
||||
new encoding of the file names (default: ISO-8859-2)
|
||||
.PD
|
||||
.SH "AUTHORS"
|
||||
.LP
|
||||
SSHFS has been written by Miklos Szeredi <miklos@szeredi.hu>.
|
||||
.LP
|
||||
This man page was written by Bartosz Fenski <fenio@debian.org> for the
|
||||
Debian GNU/Linux distribution (but it may be used by others).
|
||||
@@ -0,0 +1,346 @@
|
||||
=======
|
||||
SSHFS
|
||||
=======
|
||||
|
||||
---------------------------------------------
|
||||
filesystem client based on SSH
|
||||
---------------------------------------------
|
||||
|
||||
:Manual section: 1
|
||||
:Manual group: User Commands
|
||||
|
||||
Synopsis
|
||||
========
|
||||
|
||||
To mount a filesystem::
|
||||
|
||||
sshfs [user@]host:[dir] mountpoint [options]
|
||||
|
||||
If *host* is a numeric IPv6 address, it needs to be enclosed in square
|
||||
brackets.
|
||||
|
||||
To unmount it::
|
||||
|
||||
fusermount3 -u mountpoint # Linux
|
||||
umount mountpoint # OS X, FreeBSD
|
||||
|
||||
Description
|
||||
===========
|
||||
|
||||
SSHFS allows you to mount a remote filesystem using SSH (more precisely, the SFTP
|
||||
subsystem). Most SSH servers support and enable this SFTP access by default, so SSHFS is
|
||||
very simple to use - there's nothing to do on the server-side.
|
||||
|
||||
By default, file permissions are ignored by SSHFS. Any user that can access the filesystem
|
||||
will be able to perform any operation that the remote server permits - based on the
|
||||
credentials that were used to connect to the server. If this is undesired, local
|
||||
permission checking can be enabled with ``-o default_permissions``.
|
||||
|
||||
By default, only the mounting user will be able to access the filesystem. Access for other
|
||||
users can be enabled by passing ``-o allow_other``. In this case you most likely also
|
||||
want to use ``-o default_permissions``.
|
||||
|
||||
It is recommended to run SSHFS as regular user (not as root). For this to work the
|
||||
mountpoint must be owned by the user. If username is omitted SSHFS will use the local
|
||||
username. If the directory is omitted, SSHFS will mount the (remote) home directory. If
|
||||
you need to enter a password sshfs will ask for it (actually it just runs ssh which ask
|
||||
for the password if needed).
|
||||
|
||||
|
||||
Options
|
||||
=======
|
||||
|
||||
|
||||
-o opt,[opt...]
|
||||
mount options, see below for details. A a variety of SSH options can
|
||||
be given here as well, see the manual pages for *sftp(1)* and
|
||||
*ssh_config(5)*.
|
||||
|
||||
-h, --help
|
||||
print help and exit.
|
||||
|
||||
-V, --version
|
||||
print version information and exit.
|
||||
|
||||
-d, --debug
|
||||
print debugging information.
|
||||
|
||||
-p PORT
|
||||
equivalent to '-o port=PORT'
|
||||
|
||||
-f
|
||||
do not daemonize, stay in foreground.
|
||||
|
||||
-s
|
||||
Single threaded operation.
|
||||
|
||||
-C
|
||||
equivalent to '-o compression=yes'
|
||||
|
||||
-F ssh_configfile
|
||||
specifies alternative ssh configuration file
|
||||
|
||||
-1
|
||||
equivalent to '-o ssh_protocol=1'
|
||||
|
||||
-o reconnect
|
||||
automatically reconnect to server if connection is
|
||||
interrupted. Attempts to access files that were opened before the
|
||||
reconnection will give errors and need to be re-opened.
|
||||
|
||||
-o delay_connect
|
||||
Don't immediately connect to server, wait until mountpoint is first
|
||||
accessed.
|
||||
|
||||
-o sshfs_sync
|
||||
synchronous writes. This will slow things down, but may be useful
|
||||
in some situations.
|
||||
|
||||
-o no_readahead
|
||||
Only read exactly the data that was requested, instead of
|
||||
speculatively reading more to anticipate the next read request.
|
||||
|
||||
-o sync_readdir
|
||||
synchronous readdir. This will slow things down, but may be useful
|
||||
in some situations.
|
||||
|
||||
-o workaround=LIST
|
||||
Enable the specified workaround. See the `Caveats` section below
|
||||
for some additional information. Possible values are:
|
||||
|
||||
:rename: Emulate overwriting an existing file by deleting and
|
||||
renaming.
|
||||
:renamexdev: Make rename fail with EXDEV instead of the default EPERM
|
||||
to allow moving files across remote filesystems.
|
||||
:truncate: Work around servers that don't support truncate by
|
||||
coping the whole file, truncating it locally, and sending it
|
||||
back.
|
||||
:fstat: Work around broken servers that don't support *fstat()* by
|
||||
using *stat* instead.
|
||||
:buflimit: Work around OpenSSH "buffer fillup" bug.
|
||||
:createmode: Work around broken servers that produce an error when passing a
|
||||
non-zero mode to create, by always passing a mode of 0.
|
||||
|
||||
-o idmap=TYPE
|
||||
How to map remote UID/GIDs to local values. Possible values are:
|
||||
|
||||
:none: no translation of the ID space (default).
|
||||
|
||||
:user: map the UID/GID of the remote user to UID/GID of the
|
||||
mounting user.
|
||||
|
||||
:file: translate UIDs/GIDs based upon the contents of `--uidfile`
|
||||
and `--gidfile`.
|
||||
|
||||
-o uidfile=FILE
|
||||
file containing ``username:uid`` mappings for `-o idmap=file`
|
||||
|
||||
-o gidfile=FILE
|
||||
file containing ``groupname:gid`` mappings for `-o idmap=file`
|
||||
|
||||
-o nomap=TYPE
|
||||
with idmap=file, how to handle missing mappings:
|
||||
|
||||
:ignore: don't do any re-mapping
|
||||
:error: return an error (default)
|
||||
|
||||
-o ssh_command=CMD
|
||||
execute CMD instead of 'ssh'
|
||||
|
||||
-o ssh_protocol=N
|
||||
ssh protocol to use (default: 2)
|
||||
|
||||
-o sftp_server=SERV
|
||||
path to sftp server or subsystem (default: sftp)
|
||||
|
||||
-o directport=PORT
|
||||
directly connect to PORT bypassing ssh
|
||||
|
||||
-o vsock=CID:PORT
|
||||
directly connect using a vsock to CID:PORT bypassing ssh
|
||||
|
||||
-o passive
|
||||
communicate over stdin and stdout bypassing network. Useful for
|
||||
mounting local filesystem on the remote side. An example using
|
||||
dpipe command would be ``dpipe /usr/lib/openssh/sftp-server = ssh
|
||||
RemoteHostname sshfs :/directory/to/be/shared ~/mnt/src -o passive``
|
||||
|
||||
-o disable_hardlink
|
||||
With this option set, attempts to call `link(2)` will fail with
|
||||
error code ENOSYS.
|
||||
|
||||
-o transform_symlinks
|
||||
transform absolute symlinks on remote side to relative
|
||||
symlinks. This means that if e.g. on the server side
|
||||
``/foo/bar/com`` is a symlink to ``/foo/blub``, SSHFS will
|
||||
transform the link target to ``../blub`` on the client side.
|
||||
|
||||
-o follow_symlinks
|
||||
follow symlinks on the server, i.e. present them as regular
|
||||
files on the client. If a symlink is dangling (i.e, the target does
|
||||
not exist) the behavior depends on the remote server - the entry
|
||||
may appear as a symlink on the client, or it may appear as a
|
||||
regular file that cannot be accessed.
|
||||
|
||||
-o no_check_root
|
||||
don't check for existence of 'dir' on server
|
||||
|
||||
-o password_stdin
|
||||
read password from stdin (only for pam_mount!)
|
||||
|
||||
-o dir_cache=BOOL
|
||||
Enables (*yes*) or disables (*no*) the SSHFS directory cache. The
|
||||
directory cache holds the names of directory entries. Enabling it
|
||||
allows `readdir(3)` system calls to be processed without network
|
||||
access.
|
||||
|
||||
-o dcache_max_size=N
|
||||
sets the maximum size of the directory cache.
|
||||
|
||||
-o dcache_timeout=N
|
||||
sets timeout for directory cache in seconds.
|
||||
|
||||
-o dcache_{stat,link,dir}_timeout=N
|
||||
sets separate timeout for {attributes, symlinks, names} in the
|
||||
directory cache.
|
||||
|
||||
-o dcache_clean_interval=N
|
||||
sets the interval for automatic cleaning of the directory cache.
|
||||
|
||||
-o dcache_min_clean_interval=N
|
||||
sets the interval for forced cleaning of the directory cache
|
||||
when full.
|
||||
|
||||
-o direct_io
|
||||
This option disables the use of page cache (file content cache) in
|
||||
the kernel for this filesystem.
|
||||
This has several affects:
|
||||
|
||||
1. Each read() or write() system call will initiate one or more read or
|
||||
write operations, data will not be cached in the kernel.
|
||||
|
||||
2. The return value of the read() and write() system calls will correspond
|
||||
to the return values of the read and write operations. This is useful
|
||||
for example if the file size is not known in advance (before reading it).
|
||||
e.g. /proc filesystem
|
||||
|
||||
-o max_conns=N
|
||||
sets the maximum number of simultaneous SSH connections
|
||||
to use. Each connection is established with a separate SSH process.
|
||||
The primary purpose of this feature is to improve the responsiveness of the
|
||||
file system during large file transfers. When using more than once
|
||||
connection, the *password_stdin* and *passive* options can not be
|
||||
used, and the *buflimit* workaround is not supported.
|
||||
|
||||
In addition, SSHFS accepts several options common to all FUSE file
|
||||
systems. These are described in the `mount.fuse` manpage (look
|
||||
for "general", "libfuse specific", and "high-level API" options).
|
||||
|
||||
Caveats / Workarounds
|
||||
=====================
|
||||
|
||||
Hardlinks
|
||||
~~~~~~~~~
|
||||
|
||||
If the SSH server supports the *hardlinks* extension, SSHFS will allow
|
||||
you to create hardlinks. However, hardlinks will always appear as
|
||||
individual files when seen through an SSHFS mount, i.e. they will
|
||||
appear to have different inodes and an *st_nlink* value of 1.
|
||||
|
||||
|
||||
Rename
|
||||
~~~~~~
|
||||
|
||||
Some SSH servers do not support atomically overwriting the destination
|
||||
when renaming a file. In this case you will get an error when you
|
||||
attempt to rename a file and the destination already exists. A
|
||||
workaround is to first remove the destination file, and then do the
|
||||
rename. SSHFS can do this automatically if you call it with `-o
|
||||
workaround=rename`. However, in this case it is still possible that
|
||||
someone (or something) recreates the destination file after SSHFS has
|
||||
removed it, but before SSHFS had the time to rename the old file. In
|
||||
this case, the rename will still fail.
|
||||
|
||||
|
||||
Permission denied when moving files across remote filesystems
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Most SFTP servers return only a generic "failure" when failing to rename
|
||||
across filesystem boundaries (EXDEV). sshfs normally converts this generic
|
||||
failure to a permission denied error (EPERM). If the option ``-o
|
||||
workaround=renamexdev`` is given, generic failures will be considered EXDEV
|
||||
errors which will make programs like `mv(1)` attempt to actually move the
|
||||
file after the failed rename.
|
||||
|
||||
|
||||
SSHFS hangs for no apparent reason
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In some cases, attempts to access the SSHFS mountpoint may freeze if
|
||||
no filesystem activity has occurred for some time. This is typically
|
||||
caused by the SSH connection being dropped because of inactivity
|
||||
without SSHFS being informed about that. As a workaround, you can try
|
||||
to mount with ``-o ServerAliveInterval=15``. This will force the SSH
|
||||
connection to stay alive even if you have no activity.
|
||||
|
||||
|
||||
SSHFS hangs after the connection was interrupted
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default, network operations in SSHFS run without timeouts, mirroring the
|
||||
default behavior of SSH itself. As a consequence, if the connection to the
|
||||
remote host is interrupted (e.g. because a network cable was removed),
|
||||
operations on files or directories under the mountpoint will block until the
|
||||
connection is either restored or closed altogether (e.g. manually).
|
||||
Applications that try to access such files or directories will generally appear
|
||||
to "freeze" when this happens.
|
||||
|
||||
If it is acceptable to discard data being read or written, a quick workaround
|
||||
is to kill the responsible ``sshfs`` process, which will make any blocking
|
||||
operations on the mounted filesystem error out and thereby "unfreeze" the
|
||||
relevant applications. Note that force unmounting with ``fusermount -zu``, on
|
||||
the other hand, does not help in this case and will leave read/write operations
|
||||
in the blocking state.
|
||||
|
||||
For a more automatic solution, one can use the ``-o ServerAliveInterval=15``
|
||||
option mentioned above, which will drop the connection after not receiving a
|
||||
response for 3 * 15 = 45 seconds from the remote host. By also supplying ``-o
|
||||
reconnect``, one can ensure that the connection is re-established as soon as
|
||||
possible afterwards. As before, this will naturally lead to loss of data that
|
||||
was in the process of being read or written at the time when the connection was
|
||||
interrupted.
|
||||
|
||||
|
||||
Mounting from /etc/fstab
|
||||
========================
|
||||
|
||||
To mount an SSHFS filesystem from ``/etc/fstab``, simply use ``sshfs``
|
||||
as the file system type. (For backwards compatibility, you may also
|
||||
use ``fuse.sshfs``).
|
||||
|
||||
|
||||
See also
|
||||
========
|
||||
|
||||
The `mount.fuse(8)` manpage.
|
||||
|
||||
Getting Help
|
||||
============
|
||||
|
||||
If you need help, please ask on the <fuse-sshfs@lists.sourceforge.net>
|
||||
mailing list (subscribe at
|
||||
https://lists.sourceforge.net/lists/listinfo/fuse-sshfs).
|
||||
|
||||
Please report any bugs on the GitHub issue tracker at
|
||||
https://github.com/libfuse/libfuse/issues.
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
SSHFS is currently maintained by Nikolaus Rath <Nikolaus@rath.org>,
|
||||
and was created by Miklos Szeredi <miklos@szeredi.hu>.
|
||||
|
||||
This man page was originally written by Bartosz Fenski
|
||||
<fenio@debian.org> for the Debian GNU/Linux distribution (but it may
|
||||
be used by others).
|
||||
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
machine=$(uname -m)
|
||||
mkdir "build-$machine"
|
||||
cd "build-$machine"
|
||||
meson ..
|
||||
ninja
|
||||
@@ -0,0 +1,112 @@
|
||||
import sys
|
||||
import pytest
|
||||
import time
|
||||
import re
|
||||
|
||||
# If a test fails, wait a moment before retrieving the captured stdout/stderr.
|
||||
# When using a server process, this makes sure that we capture any potential
|
||||
# output of the server that comes *after* a test has failed. For example, if a
|
||||
# request handler raises an exception, the server first signals an error to
|
||||
# FUSE (causing the test to fail), and then logs the exception. Without the
|
||||
# extra delay, the exception will go into nowhere.
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_pyfunc_call(pyfuncitem):
|
||||
outcome = yield
|
||||
failed = outcome.excinfo is not None
|
||||
if failed:
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def pass_capfd(request, capfd):
|
||||
"""Provide capfd object to UnitTest instances"""
|
||||
request.instance.capfd = capfd
|
||||
|
||||
|
||||
def check_test_output(capfd):
|
||||
(stdout, stderr) = capfd.readouterr()
|
||||
|
||||
# Write back what we've read (so that it will still be printed.
|
||||
sys.stdout.write(stdout)
|
||||
sys.stderr.write(stderr)
|
||||
|
||||
# Strip out false positives
|
||||
for (pattern, flags, count) in capfd.false_positives:
|
||||
cp = re.compile(pattern, flags)
|
||||
(stdout, cnt) = cp.subn("", stdout, count=count)
|
||||
if count == 0 or count - cnt > 0:
|
||||
stderr = cp.sub("", stderr, count=count - cnt)
|
||||
|
||||
patterns = [
|
||||
r"\b{}\b".format(x)
|
||||
for x in (
|
||||
"exception",
|
||||
"error",
|
||||
"warning",
|
||||
"fatal",
|
||||
"traceback",
|
||||
"fault",
|
||||
"crash(?:ed)?",
|
||||
"abort(?:ed)",
|
||||
"uninitiali[zs]ed",
|
||||
)
|
||||
]
|
||||
patterns += ["^==[0-9]+== "]
|
||||
for pattern in patterns:
|
||||
cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE)
|
||||
hit = cp.search(stderr)
|
||||
if hit:
|
||||
raise AssertionError(
|
||||
'Suspicious output to stderr (matched "%s")' % hit.group(0)
|
||||
)
|
||||
hit = cp.search(stdout)
|
||||
if hit:
|
||||
raise AssertionError(
|
||||
'Suspicious output to stdout (matched "%s")' % hit.group(0)
|
||||
)
|
||||
|
||||
|
||||
def register_output(self, pattern, count=1, flags=re.MULTILINE):
|
||||
"""Register *pattern* as false positive for output checking
|
||||
|
||||
This prevents the test from failing because the output otherwise
|
||||
appears suspicious.
|
||||
"""
|
||||
|
||||
self.false_positives.append((pattern, flags, count))
|
||||
|
||||
|
||||
# This is a terrible hack that allows us to access the fixtures from the
|
||||
# pytest_runtest_call hook. Among a lot of other hidden assumptions, it probably
|
||||
# relies on tests running sequential (i.e., don't dare to use e.g. the xdist
|
||||
# plugin)
|
||||
current_capfd = None
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def save_cap_fixtures(request, capfd):
|
||||
global current_capfd
|
||||
capfd.false_positives = []
|
||||
|
||||
# Monkeypatch in a function to register false positives
|
||||
type(capfd).register_output = register_output
|
||||
|
||||
if request.config.getoption("capture") == "no":
|
||||
capfd = None
|
||||
current_capfd = capfd
|
||||
bak = current_capfd
|
||||
yield
|
||||
|
||||
# Try to catch problems with this hack (e.g. when running tests
|
||||
# simultaneously)
|
||||
assert bak is current_capfd
|
||||
current_capfd = None
|
||||
|
||||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_runtest_call(item):
|
||||
capfd = current_capfd
|
||||
if capfd is not None:
|
||||
check_test_output(capfd)
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
pip3 install --user pre-commit
|
||||
pre-commit run --all-files --show-diff-on-failure
|
||||
@@ -0,0 +1,11 @@
|
||||
# Suppression file for address sanitizer.
|
||||
|
||||
# There are some leaks in command line option parsing. They should be
|
||||
# fixed at some point, but are harmless since the consume just a small,
|
||||
# constant amount of memory and do not grow.
|
||||
leak:fuse_opt_parse
|
||||
|
||||
|
||||
# Leaks in fusermount3 are harmless as well (it's a short-lived
|
||||
# process) - but patches are welcome!
|
||||
leak:fusermount.c
|
||||
@@ -0,0 +1,11 @@
|
||||
test_scripts = [ 'conftest.py', 'pytest.ini', 'test_sshfs.py',
|
||||
'util.py' ]
|
||||
custom_target('test_scripts', input: test_scripts,
|
||||
output: test_scripts, build_by_default: true,
|
||||
command: ['cp', '-fPp',
|
||||
'@INPUT@', meson.current_build_dir() ])
|
||||
|
||||
# Provide something helpful when running 'ninja test'
|
||||
wrong_cmd = executable('wrong_command', 'wrong_command.c',
|
||||
install: false)
|
||||
test('wrong_cmd', wrong_cmd)
|
||||
@@ -0,0 +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
|
||||
Executable
+912
@@ -0,0 +1,912 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
sys.exit(pytest.main([__file__] + sys.argv[1:]))
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
import stat
|
||||
import shutil
|
||||
import filecmp
|
||||
import errno
|
||||
from tempfile import NamedTemporaryFile
|
||||
from util import (
|
||||
wait_for_mount,
|
||||
umount,
|
||||
cleanup,
|
||||
base_cmdline,
|
||||
basename,
|
||||
fuse_test_marker,
|
||||
safe_sleep,
|
||||
os_create,
|
||||
os_open,
|
||||
)
|
||||
from os.path import join as pjoin
|
||||
|
||||
TEST_FILE = __file__
|
||||
|
||||
pytestmark = fuse_test_marker()
|
||||
|
||||
with open(TEST_FILE, "rb") as fh:
|
||||
TEST_DATA = fh.read()
|
||||
|
||||
|
||||
def name_generator(__ctr=[0]) -> str:
|
||||
"""Generate a fresh filename on each call"""
|
||||
|
||||
__ctr[0] += 1
|
||||
return f"testfile_{__ctr[0]}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"debug",
|
||||
[pytest.param(False, id="debug=false"), pytest.param(True, id="debug=true")],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"cache_timeout",
|
||||
[pytest.param(0, id="cache_timeout=0"), pytest.param(1, id="cache_timeout=1")],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"sync_rd",
|
||||
[pytest.param(True, id="sync_rd=true"), pytest.param(False, id="sync_rd=false")],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"multiconn",
|
||||
[
|
||||
pytest.param(True, id="multiconn=true"),
|
||||
pytest.param(False, id="multiconn=false"),
|
||||
],
|
||||
)
|
||||
def test_sshfs(
|
||||
tmpdir, debug: bool, cache_timeout: int, sync_rd: bool, multiconn: bool, capfd
|
||||
) -> None:
|
||||
|
||||
# Avoid false positives from debug messages
|
||||
# if debug:
|
||||
# capfd.register_output(r'^ unique: [0-9]+, error: -[0-9]+ .+$',
|
||||
# count=0)
|
||||
|
||||
# Avoid false positives from storing key for localhost
|
||||
capfd.register_output(r"^Warning: Permanently added 'localhost' .+", count=0)
|
||||
|
||||
# Test if we can ssh into localhost without password
|
||||
try:
|
||||
res = subprocess.call(
|
||||
[
|
||||
"ssh",
|
||||
"-o",
|
||||
"StrictHostKeyChecking=no",
|
||||
"-o",
|
||||
"KbdInteractiveAuthentication=no",
|
||||
"-o",
|
||||
"ChallengeResponseAuthentication=no",
|
||||
"-o",
|
||||
"PasswordAuthentication=no",
|
||||
"localhost",
|
||||
"--",
|
||||
"true",
|
||||
],
|
||||
stdin=subprocess.DEVNULL,
|
||||
timeout=10,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
res = 1
|
||||
if res != 0:
|
||||
pytest.fail("Unable to ssh into localhost without password prompt.")
|
||||
|
||||
mnt_dir = str(tmpdir.mkdir("mnt"))
|
||||
src_dir = str(tmpdir.mkdir("src"))
|
||||
|
||||
cmdline = base_cmdline + [
|
||||
pjoin(basename, "sshfs"),
|
||||
"-f",
|
||||
f"localhost:{src_dir}",
|
||||
mnt_dir,
|
||||
]
|
||||
if debug:
|
||||
cmdline += ["-o", "sshfs_debug"]
|
||||
|
||||
if sync_rd:
|
||||
cmdline += ["-o", "sync_readdir"]
|
||||
|
||||
# SSHFS Cache
|
||||
if cache_timeout == 0:
|
||||
cmdline += ["-o", "dir_cache=no"]
|
||||
else:
|
||||
cmdline += ["-o", f"dcache_timeout={cache_timeout}", "-o", "dir_cache=yes"]
|
||||
|
||||
# FUSE Cache
|
||||
cmdline += ["-o", "entry_timeout=0", "-o", "attr_timeout=0"]
|
||||
|
||||
if multiconn:
|
||||
cmdline += ["-o", "max_conns=3"]
|
||||
|
||||
new_env = dict(os.environ) # copy, don't modify
|
||||
|
||||
# Abort on warnings from glib
|
||||
new_env["G_DEBUG"] = "fatal-warnings"
|
||||
|
||||
mount_process = subprocess.Popen(cmdline, env=new_env)
|
||||
try:
|
||||
wait_for_mount(mount_process, mnt_dir)
|
||||
|
||||
tst_statvfs(src_dir, mnt_dir)
|
||||
tst_readdir(src_dir, mnt_dir)
|
||||
tst_open_read(src_dir, mnt_dir)
|
||||
tst_open_write(src_dir, mnt_dir)
|
||||
tst_append(src_dir, mnt_dir)
|
||||
tst_seek(src_dir, mnt_dir)
|
||||
tst_create(mnt_dir)
|
||||
tst_passthrough(src_dir, mnt_dir, cache_timeout)
|
||||
tst_mkdir(mnt_dir)
|
||||
tst_rmdir(src_dir, mnt_dir, cache_timeout)
|
||||
tst_rename(mnt_dir)
|
||||
tst_rename_over(mnt_dir)
|
||||
tst_chmod(mnt_dir)
|
||||
tst_fsync(src_dir, mnt_dir)
|
||||
tst_unlink(src_dir, mnt_dir, cache_timeout)
|
||||
tst_symlink(mnt_dir)
|
||||
if os.getuid() == 0:
|
||||
tst_chown(mnt_dir)
|
||||
|
||||
# SSHFS only supports one second resolution when setting
|
||||
# file timestamps.
|
||||
tst_utimens(mnt_dir, tol=1)
|
||||
tst_utimens_now(mnt_dir)
|
||||
|
||||
tst_link(mnt_dir, cache_timeout)
|
||||
tst_truncate_path(mnt_dir)
|
||||
tst_truncate_fd(mnt_dir)
|
||||
tst_open_unlink(mnt_dir)
|
||||
tst_open_writeonly_read(mnt_dir)
|
||||
tst_access(mnt_dir)
|
||||
tst_mkdir_exist(mnt_dir)
|
||||
tst_readdir_repeated(mnt_dir)
|
||||
tst_rename_sibling(mnt_dir)
|
||||
tst_rename_open_release(mnt_dir)
|
||||
except Exception as exc:
|
||||
cleanup(mount_process, mnt_dir)
|
||||
raise exc
|
||||
else:
|
||||
umount(mount_process, mnt_dir)
|
||||
|
||||
|
||||
def tst_unlink(src_dir, mnt_dir, cache_timeout):
|
||||
name = name_generator()
|
||||
fullname = mnt_dir + "/" + name
|
||||
with open(pjoin(src_dir, name), "wb") as fh:
|
||||
fh.write(b"hello")
|
||||
if cache_timeout:
|
||||
safe_sleep(cache_timeout + 1)
|
||||
assert name in os.listdir(mnt_dir)
|
||||
os.unlink(fullname)
|
||||
with pytest.raises(OSError) as exc_info:
|
||||
os.stat(fullname)
|
||||
assert exc_info.value.errno == errno.ENOENT
|
||||
assert name not in os.listdir(mnt_dir)
|
||||
assert name not in os.listdir(src_dir)
|
||||
|
||||
|
||||
def tst_mkdir(mnt_dir):
|
||||
dirname = name_generator()
|
||||
fullname = mnt_dir + "/" + dirname
|
||||
os.mkdir(fullname)
|
||||
fstat = os.stat(fullname)
|
||||
assert stat.S_ISDIR(fstat.st_mode)
|
||||
assert os.listdir(fullname) == []
|
||||
assert fstat.st_nlink in (1, 2)
|
||||
assert dirname in os.listdir(mnt_dir)
|
||||
|
||||
|
||||
def tst_rmdir(src_dir, mnt_dir, cache_timeout):
|
||||
name = name_generator()
|
||||
fullname = mnt_dir + "/" + name
|
||||
os.mkdir(pjoin(src_dir, name))
|
||||
if cache_timeout:
|
||||
safe_sleep(cache_timeout + 1)
|
||||
assert name in os.listdir(mnt_dir)
|
||||
os.rmdir(fullname)
|
||||
with pytest.raises(OSError) as exc_info:
|
||||
os.stat(fullname)
|
||||
assert exc_info.value.errno == errno.ENOENT
|
||||
assert name not in os.listdir(mnt_dir)
|
||||
assert name not in os.listdir(src_dir)
|
||||
|
||||
|
||||
def tst_rename(mnt_dir):
|
||||
src_name = pjoin(mnt_dir, name_generator())
|
||||
dst_name = pjoin(mnt_dir, name_generator())
|
||||
data = b"rename test data\n"
|
||||
|
||||
with open(src_name, "wb") as fh:
|
||||
fh.write(data)
|
||||
assert os.path.exists(src_name)
|
||||
|
||||
os.rename(src_name, dst_name)
|
||||
|
||||
assert not os.path.exists(src_name)
|
||||
assert os.path.basename(src_name) not in os.listdir(mnt_dir)
|
||||
assert os.path.basename(dst_name) in os.listdir(mnt_dir)
|
||||
with open(dst_name, "rb") as fh:
|
||||
assert fh.read() == data
|
||||
|
||||
os.unlink(dst_name)
|
||||
|
||||
|
||||
def tst_rename_over(mnt_dir):
|
||||
src_name = pjoin(mnt_dir, name_generator())
|
||||
dst_name = pjoin(mnt_dir, name_generator())
|
||||
src_data = b"source content\n"
|
||||
dst_data = b"destination content\n"
|
||||
|
||||
with open(src_name, "wb") as fh:
|
||||
fh.write(src_data)
|
||||
with open(dst_name, "wb") as fh:
|
||||
fh.write(dst_data)
|
||||
|
||||
os.rename(src_name, dst_name)
|
||||
|
||||
assert not os.path.exists(src_name)
|
||||
assert os.path.basename(src_name) not in os.listdir(mnt_dir)
|
||||
with open(dst_name, "rb") as fh:
|
||||
assert fh.read() == src_data
|
||||
|
||||
os.unlink(dst_name)
|
||||
|
||||
|
||||
def tst_chmod(mnt_dir):
|
||||
filename = pjoin(mnt_dir, name_generator())
|
||||
with open(filename, "wb") as fh:
|
||||
fh.write(b"chmod test\n")
|
||||
|
||||
os.chmod(filename, 0o644)
|
||||
fstat = os.stat(filename)
|
||||
assert stat.S_IMODE(fstat.st_mode) == 0o644
|
||||
|
||||
os.chmod(filename, 0o755)
|
||||
fstat = os.stat(filename)
|
||||
assert stat.S_IMODE(fstat.st_mode) == 0o755
|
||||
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def tst_fsync(src_dir, mnt_dir):
|
||||
name = name_generator()
|
||||
mnt_name = pjoin(mnt_dir, name)
|
||||
src_name = pjoin(src_dir, name)
|
||||
data = b"fsync test data\n"
|
||||
|
||||
fd = os.open(mnt_name, os.O_CREAT | os.O_WRONLY)
|
||||
try:
|
||||
os.write(fd, data)
|
||||
os.fsync(fd)
|
||||
# Read from backing store while fd is still open, before
|
||||
# close/release has a chance to flush
|
||||
with open(src_name, "rb") as fh:
|
||||
assert fh.read() == data
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
os.unlink(mnt_name)
|
||||
|
||||
|
||||
def tst_symlink(mnt_dir):
|
||||
linkname = name_generator()
|
||||
fullname = mnt_dir + "/" + linkname
|
||||
os.symlink("/imaginary/dest", fullname)
|
||||
fstat = os.lstat(fullname)
|
||||
assert stat.S_ISLNK(fstat.st_mode)
|
||||
assert os.readlink(fullname) == "/imaginary/dest"
|
||||
assert fstat.st_nlink == 1
|
||||
assert linkname in os.listdir(mnt_dir)
|
||||
|
||||
os.unlink(fullname)
|
||||
assert linkname not in os.listdir(mnt_dir)
|
||||
|
||||
|
||||
def tst_create(mnt_dir):
|
||||
name = name_generator()
|
||||
fullname = pjoin(mnt_dir, name)
|
||||
with pytest.raises(OSError) as exc_info:
|
||||
os.stat(fullname)
|
||||
assert exc_info.value.errno == errno.ENOENT
|
||||
assert name not in os.listdir(mnt_dir)
|
||||
|
||||
fd = os.open(fullname, os.O_CREAT | os.O_RDWR)
|
||||
os.close(fd)
|
||||
|
||||
assert name in os.listdir(mnt_dir)
|
||||
fstat = os.lstat(fullname)
|
||||
assert stat.S_ISREG(fstat.st_mode)
|
||||
assert fstat.st_nlink == 1
|
||||
assert fstat.st_size == 0
|
||||
|
||||
|
||||
def tst_chown(mnt_dir):
|
||||
filename = pjoin(mnt_dir, name_generator())
|
||||
os.mkdir(filename)
|
||||
fstat = os.lstat(filename)
|
||||
uid = fstat.st_uid
|
||||
gid = fstat.st_gid
|
||||
|
||||
uid_new = uid + 1
|
||||
os.chown(filename, uid_new, -1)
|
||||
fstat = os.lstat(filename)
|
||||
assert fstat.st_uid == uid_new
|
||||
assert fstat.st_gid == gid
|
||||
|
||||
gid_new = gid + 1
|
||||
os.chown(filename, -1, gid_new)
|
||||
fstat = os.lstat(filename)
|
||||
assert fstat.st_uid == uid_new
|
||||
assert fstat.st_gid == gid_new
|
||||
|
||||
|
||||
def tst_open_read(src_dir, mnt_dir):
|
||||
name = name_generator()
|
||||
with open(pjoin(src_dir, name), "wb") as fh_out, open(TEST_FILE, "rb") as fh_in:
|
||||
shutil.copyfileobj(fh_in, fh_out)
|
||||
|
||||
assert filecmp.cmp(pjoin(mnt_dir, name), TEST_FILE, False)
|
||||
|
||||
|
||||
def tst_open_write(src_dir, mnt_dir):
|
||||
name = name_generator()
|
||||
fd = os.open(pjoin(src_dir, name), os.O_CREAT | os.O_RDWR)
|
||||
os.close(fd)
|
||||
fullname = pjoin(mnt_dir, name)
|
||||
with open(fullname, "wb") as fh_out, open(TEST_FILE, "rb") as fh_in:
|
||||
shutil.copyfileobj(fh_in, fh_out)
|
||||
|
||||
assert filecmp.cmp(fullname, TEST_FILE, False)
|
||||
|
||||
|
||||
def tst_append(src_dir, mnt_dir):
|
||||
name = name_generator()
|
||||
os_create(pjoin(src_dir, name))
|
||||
fullname = pjoin(mnt_dir, name)
|
||||
with os_open(fullname, os.O_WRONLY) as fd:
|
||||
os.write(fd, b"foo\n")
|
||||
with os_open(fullname, os.O_WRONLY | os.O_APPEND) as fd:
|
||||
os.write(fd, b"bar\n")
|
||||
|
||||
with open(fullname, "rb") as fh:
|
||||
assert fh.read() == b"foo\nbar\n"
|
||||
|
||||
|
||||
def tst_seek(src_dir, mnt_dir):
|
||||
name = name_generator()
|
||||
os_create(pjoin(src_dir, name))
|
||||
fullname = pjoin(mnt_dir, name)
|
||||
with os_open(fullname, os.O_WRONLY) as fd:
|
||||
os.lseek(fd, 1, os.SEEK_SET)
|
||||
os.write(fd, b"foobar\n")
|
||||
with os_open(fullname, os.O_WRONLY) as fd:
|
||||
os.lseek(fd, 4, os.SEEK_SET)
|
||||
os.write(fd, b"com")
|
||||
|
||||
with open(fullname, "rb") as fh:
|
||||
assert fh.read() == b"\0foocom\n"
|
||||
|
||||
|
||||
def tst_open_unlink(mnt_dir):
|
||||
name = pjoin(mnt_dir, name_generator())
|
||||
data1 = b"foo"
|
||||
data2 = b"bar"
|
||||
fullname = pjoin(mnt_dir, name)
|
||||
with open(fullname, "wb+", buffering=0) as fh:
|
||||
fh.write(data1)
|
||||
os.unlink(fullname)
|
||||
with pytest.raises(OSError) as exc_info:
|
||||
os.stat(fullname)
|
||||
assert exc_info.value.errno == errno.ENOENT
|
||||
assert name not in os.listdir(mnt_dir)
|
||||
fh.write(data2)
|
||||
fh.seek(0)
|
||||
assert fh.read() == data1 + data2
|
||||
|
||||
|
||||
def tst_statvfs(src_dir, mnt_dir):
|
||||
vfs = os.statvfs(mnt_dir)
|
||||
ref = os.statvfs(src_dir)
|
||||
# When the server supports statvfs@openssh.com, values should
|
||||
# match the backing store. Otherwise sshfs returns synthetic
|
||||
# values that still pass the loose checks.
|
||||
if vfs.f_bsize == ref.f_bsize:
|
||||
assert vfs.f_frsize == ref.f_frsize
|
||||
assert vfs.f_blocks == ref.f_blocks
|
||||
assert vfs.f_namemax == ref.f_namemax
|
||||
else:
|
||||
assert vfs.f_bsize > 0
|
||||
assert vfs.f_blocks > 0
|
||||
assert vfs.f_namemax > 0
|
||||
|
||||
|
||||
def tst_open_writeonly_read(mnt_dir):
|
||||
name = pjoin(mnt_dir, name_generator())
|
||||
fd = os.open(name, os.O_CREAT | os.O_WRONLY)
|
||||
try:
|
||||
os.write(fd, b"hello")
|
||||
with pytest.raises(OSError) as exc_info:
|
||||
os.read(fd, 10)
|
||||
assert exc_info.value.errno == errno.EBADF
|
||||
finally:
|
||||
os.close(fd)
|
||||
os.unlink(name)
|
||||
|
||||
|
||||
def tst_access(mnt_dir):
|
||||
filename = pjoin(mnt_dir, name_generator())
|
||||
with open(filename, "wb") as fh:
|
||||
fh.write(b"test")
|
||||
os.chmod(filename, 0o644)
|
||||
assert os.access(filename, os.R_OK)
|
||||
if os.getuid() != 0:
|
||||
assert not os.access(filename, os.X_OK)
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def tst_mkdir_exist(mnt_dir):
|
||||
name = name_generator()
|
||||
fullname = pjoin(mnt_dir, name)
|
||||
os.mkdir(fullname)
|
||||
with pytest.raises(OSError) as exc_info:
|
||||
os.mkdir(fullname)
|
||||
assert exc_info.value.errno == errno.EEXIST
|
||||
os.rmdir(fullname)
|
||||
|
||||
|
||||
def tst_readdir_repeated(mnt_dir):
|
||||
dirname = pjoin(mnt_dir, name_generator())
|
||||
os.mkdir(dirname)
|
||||
names = []
|
||||
for i in range(5):
|
||||
n = name_generator()
|
||||
names.append(n)
|
||||
with open(pjoin(dirname, n), "wb") as fh:
|
||||
fh.write(b"x")
|
||||
|
||||
# Verify repeated directory listings return consistent results
|
||||
listing1 = sorted(os.listdir(dirname))
|
||||
listing2 = sorted(os.listdir(dirname))
|
||||
assert listing1 == sorted(names)
|
||||
assert listing1 == listing2
|
||||
|
||||
for n in names:
|
||||
os.unlink(pjoin(dirname, n))
|
||||
os.rmdir(dirname)
|
||||
|
||||
|
||||
def tst_rename_sibling(mnt_dir):
|
||||
# Verify renaming one file doesn't break access to a sibling
|
||||
name_a = pjoin(mnt_dir, name_generator())
|
||||
name_b = pjoin(mnt_dir, name_generator())
|
||||
name_c = pjoin(mnt_dir, name_generator())
|
||||
|
||||
with open(name_a, "wb") as fh:
|
||||
fh.write(b"aaa")
|
||||
with open(name_b, "wb") as fh:
|
||||
fh.write(b"bbb")
|
||||
|
||||
os.rename(name_a, name_c)
|
||||
|
||||
assert not os.path.exists(name_a)
|
||||
assert os.path.exists(name_b)
|
||||
with open(name_b, "rb") as fh:
|
||||
assert fh.read() == b"bbb"
|
||||
|
||||
os.unlink(name_b)
|
||||
os.unlink(name_c)
|
||||
|
||||
|
||||
def tst_rename_open_release(mnt_dir):
|
||||
src = pjoin(mnt_dir, name_generator())
|
||||
dst = pjoin(mnt_dir, name_generator())
|
||||
|
||||
fd = os.open(src, os.O_CREAT | os.O_RDWR)
|
||||
try:
|
||||
os.write(fd, b"data")
|
||||
os.rename(src, dst)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
assert not os.path.exists(src)
|
||||
with open(dst, "rb") as fh:
|
||||
assert fh.read() == b"data"
|
||||
os.unlink(dst)
|
||||
|
||||
|
||||
def tst_link(mnt_dir, cache_timeout):
|
||||
name1 = pjoin(mnt_dir, name_generator())
|
||||
name2 = pjoin(mnt_dir, name_generator())
|
||||
shutil.copyfile(TEST_FILE, name1)
|
||||
assert filecmp.cmp(name1, TEST_FILE, False)
|
||||
|
||||
fstat1 = os.lstat(name1)
|
||||
assert fstat1.st_nlink == 1
|
||||
|
||||
os.link(name1, name2)
|
||||
|
||||
# The link operation changes st_ctime, and if we're unlucky
|
||||
# the kernel will keep the old value cached for name1, and
|
||||
# retrieve the new value for name2 (at least, this is the only
|
||||
# way I can explain the test failure). To avoid this problem,
|
||||
# we need to wait until the cached value has expired.
|
||||
if cache_timeout:
|
||||
safe_sleep(cache_timeout)
|
||||
|
||||
fstat1 = os.lstat(name1)
|
||||
fstat2 = os.lstat(name2)
|
||||
for attr in (
|
||||
"st_mode",
|
||||
"st_dev",
|
||||
"st_uid",
|
||||
"st_gid",
|
||||
"st_size",
|
||||
"st_atime",
|
||||
"st_mtime",
|
||||
"st_ctime",
|
||||
):
|
||||
assert getattr(fstat1, attr) == getattr(fstat2, attr)
|
||||
assert os.path.basename(name2) in os.listdir(mnt_dir)
|
||||
assert filecmp.cmp(name1, name2, False)
|
||||
|
||||
os.unlink(name2)
|
||||
|
||||
assert os.path.basename(name2) not in os.listdir(mnt_dir)
|
||||
with pytest.raises(FileNotFoundError):
|
||||
os.lstat(name2)
|
||||
if cache_timeout:
|
||||
safe_sleep(cache_timeout + 1)
|
||||
fstat1 = os.lstat(name1)
|
||||
assert fstat1.st_nlink == 1
|
||||
|
||||
os.unlink(name1)
|
||||
|
||||
|
||||
def tst_readdir(src_dir, mnt_dir):
|
||||
newdir = name_generator()
|
||||
src_newdir = pjoin(src_dir, newdir)
|
||||
mnt_newdir = pjoin(mnt_dir, newdir)
|
||||
file_ = src_newdir + "/" + name_generator()
|
||||
subdir = src_newdir + "/" + name_generator()
|
||||
subfile = subdir + "/" + name_generator()
|
||||
|
||||
os.mkdir(src_newdir)
|
||||
shutil.copyfile(TEST_FILE, file_)
|
||||
os.mkdir(subdir)
|
||||
shutil.copyfile(TEST_FILE, subfile)
|
||||
|
||||
listdir_is = os.listdir(mnt_newdir)
|
||||
listdir_is.sort()
|
||||
listdir_should = [os.path.basename(file_), os.path.basename(subdir)]
|
||||
listdir_should.sort()
|
||||
assert listdir_is == listdir_should
|
||||
|
||||
os.unlink(file_)
|
||||
os.unlink(subfile)
|
||||
os.rmdir(subdir)
|
||||
os.rmdir(src_newdir)
|
||||
|
||||
|
||||
def tst_truncate_path(mnt_dir):
|
||||
assert len(TEST_DATA) > 1024
|
||||
|
||||
filename = pjoin(mnt_dir, name_generator())
|
||||
with open(filename, "wb") as fh:
|
||||
fh.write(TEST_DATA)
|
||||
|
||||
fstat = os.stat(filename)
|
||||
size = fstat.st_size
|
||||
assert size == len(TEST_DATA)
|
||||
|
||||
# Add zeros at the end
|
||||
os.truncate(filename, size + 1024)
|
||||
assert os.stat(filename).st_size == size + 1024
|
||||
with open(filename, "rb") as fh:
|
||||
assert fh.read(size) == TEST_DATA
|
||||
assert fh.read(1025) == b"\0" * 1024
|
||||
|
||||
# Truncate data
|
||||
os.truncate(filename, size - 1024)
|
||||
assert os.stat(filename).st_size == size - 1024
|
||||
with open(filename, "rb") as fh:
|
||||
assert fh.read(size) == TEST_DATA[: size - 1024]
|
||||
|
||||
# Truncate to zero
|
||||
os.truncate(filename, 0)
|
||||
assert os.stat(filename).st_size == 0
|
||||
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def tst_truncate_fd(mnt_dir):
|
||||
assert len(TEST_DATA) > 1024
|
||||
with NamedTemporaryFile("w+b", 0, dir=mnt_dir) as fh:
|
||||
fd = fh.fileno()
|
||||
fh.write(TEST_DATA)
|
||||
fstat = os.fstat(fd)
|
||||
size = fstat.st_size
|
||||
assert size == len(TEST_DATA)
|
||||
|
||||
# Add zeros at the end
|
||||
os.ftruncate(fd, size + 1024)
|
||||
assert os.fstat(fd).st_size == size + 1024
|
||||
fh.seek(0)
|
||||
assert fh.read(size) == TEST_DATA
|
||||
assert fh.read(1025) == b"\0" * 1024
|
||||
|
||||
# Truncate data
|
||||
os.ftruncate(fd, size - 1024)
|
||||
assert os.fstat(fd).st_size == size - 1024
|
||||
fh.seek(0)
|
||||
assert fh.read(size) == TEST_DATA[: size - 1024]
|
||||
|
||||
# Truncate to zero via fd
|
||||
os.ftruncate(fd, 0)
|
||||
assert os.fstat(fd).st_size == 0
|
||||
|
||||
|
||||
def tst_utimens(mnt_dir, tol=0):
|
||||
filename = pjoin(mnt_dir, name_generator())
|
||||
os.mkdir(filename)
|
||||
fstat = os.lstat(filename)
|
||||
|
||||
atime = fstat.st_atime + 42.28
|
||||
mtime = fstat.st_mtime - 42.23
|
||||
if sys.version_info < (3, 3):
|
||||
os.utime(filename, (atime, mtime))
|
||||
else:
|
||||
atime_ns = fstat.st_atime_ns + int(42.28 * 1e9)
|
||||
mtime_ns = fstat.st_mtime_ns - int(42.23 * 1e9)
|
||||
os.utime(filename, None, ns=(atime_ns, mtime_ns))
|
||||
|
||||
fstat = os.lstat(filename)
|
||||
|
||||
assert abs(fstat.st_atime - atime) < tol
|
||||
assert abs(fstat.st_mtime - mtime) < tol
|
||||
if sys.version_info >= (3, 3):
|
||||
assert abs(fstat.st_atime_ns - atime_ns) < tol * 1e9
|
||||
assert abs(fstat.st_mtime_ns - mtime_ns) < tol * 1e9
|
||||
|
||||
|
||||
def tst_utimens_now(mnt_dir):
|
||||
fullname = pjoin(mnt_dir, name_generator())
|
||||
|
||||
fd = os.open(fullname, os.O_CREAT | os.O_RDWR)
|
||||
os.close(fd)
|
||||
os.utime(fullname, None)
|
||||
|
||||
fstat = os.lstat(fullname)
|
||||
# We should get now-timestamps
|
||||
assert fstat.st_atime != 0
|
||||
assert fstat.st_mtime != 0
|
||||
|
||||
|
||||
def tst_passthrough(src_dir, mnt_dir, cache_timeout):
|
||||
name = name_generator()
|
||||
src_name = pjoin(src_dir, name)
|
||||
mnt_name = pjoin(mnt_dir, name)
|
||||
assert name not in os.listdir(src_dir)
|
||||
assert name not in os.listdir(mnt_dir)
|
||||
with open(src_name, "w") as fh:
|
||||
fh.write("Hello, world")
|
||||
assert name in os.listdir(src_dir)
|
||||
if cache_timeout:
|
||||
safe_sleep(cache_timeout + 1)
|
||||
assert name in os.listdir(mnt_dir)
|
||||
src_st = os.stat(src_name)
|
||||
mnt_st = os.stat(mnt_name)
|
||||
assert src_st.st_size == mnt_st.st_size
|
||||
assert src_st.st_uid == mnt_st.st_uid
|
||||
assert src_st.st_gid == mnt_st.st_gid
|
||||
assert abs(src_st.st_mtime - mnt_st.st_mtime) <= 1
|
||||
|
||||
name = name_generator()
|
||||
src_name = pjoin(src_dir, name)
|
||||
mnt_name = pjoin(mnt_dir, name)
|
||||
assert name not in os.listdir(src_dir)
|
||||
assert name not in os.listdir(mnt_dir)
|
||||
with open(mnt_name, "w") as fh:
|
||||
fh.write("Hello, world")
|
||||
assert name in os.listdir(src_dir)
|
||||
if cache_timeout:
|
||||
safe_sleep(cache_timeout + 1)
|
||||
assert name in os.listdir(mnt_dir)
|
||||
src_st = os.stat(src_name)
|
||||
mnt_st = os.stat(mnt_name)
|
||||
assert src_st.st_size == mnt_st.st_size
|
||||
assert src_st.st_uid == mnt_st.st_uid
|
||||
assert src_st.st_gid == mnt_st.st_gid
|
||||
assert abs(src_st.st_mtime - mnt_st.st_mtime) <= 1
|
||||
|
||||
|
||||
def _check_ssh_localhost():
|
||||
try:
|
||||
res = subprocess.call(
|
||||
["ssh", "-o", "StrictHostKeyChecking=no",
|
||||
"-o", "KbdInteractiveAuthentication=no",
|
||||
"-o", "ChallengeResponseAuthentication=no",
|
||||
"-o", "PasswordAuthentication=no",
|
||||
"localhost", "--", "true"],
|
||||
stdin=subprocess.DEVNULL, timeout=10,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
res = 1
|
||||
if res != 0:
|
||||
pytest.fail("Unable to ssh into localhost without password prompt.")
|
||||
|
||||
|
||||
_mount_ctr = [0]
|
||||
|
||||
|
||||
def _mount_sshfs(tmpdir, extra_opts=None):
|
||||
"""Helper to mount sshfs with custom options. Returns (mount_process, mnt_dir, src_dir)."""
|
||||
_check_ssh_localhost()
|
||||
_mount_ctr[0] += 1
|
||||
mnt_dir = str(tmpdir.mkdir(f"mnt{_mount_ctr[0]}"))
|
||||
src_dir = str(tmpdir.mkdir(f"src{_mount_ctr[0]}"))
|
||||
|
||||
cmdline = base_cmdline + [
|
||||
pjoin(basename, "sshfs"),
|
||||
"-f",
|
||||
f"localhost:{src_dir}",
|
||||
mnt_dir,
|
||||
"-o", "entry_timeout=0",
|
||||
"-o", "attr_timeout=0",
|
||||
]
|
||||
if extra_opts:
|
||||
for opt in extra_opts:
|
||||
cmdline += ["-o", opt]
|
||||
|
||||
new_env = dict(os.environ)
|
||||
new_env["G_DEBUG"] = "fatal-warnings"
|
||||
|
||||
mount_process = subprocess.Popen(cmdline, env=new_env)
|
||||
try:
|
||||
wait_for_mount(mount_process, mnt_dir)
|
||||
except:
|
||||
cleanup(mount_process, mnt_dir)
|
||||
raise
|
||||
return mount_process, mnt_dir, src_dir
|
||||
|
||||
|
||||
def test_disable_hardlink(tmpdir, capfd):
|
||||
capfd.register_output(r"^Warning: Permanently added 'localhost' .+", count=0)
|
||||
|
||||
# Control: verify hardlinks work without disable_hardlink.
|
||||
# If the server lacks the extension, skip this test entirely.
|
||||
mount_process, mnt_dir, src_dir = _mount_sshfs(tmpdir, [])
|
||||
try:
|
||||
name1 = pjoin(mnt_dir, name_generator())
|
||||
name2 = pjoin(mnt_dir, name_generator())
|
||||
with open(name1, "wb") as fh:
|
||||
fh.write(b"test")
|
||||
try:
|
||||
os.link(name1, name2)
|
||||
except OSError:
|
||||
os.unlink(name1)
|
||||
pytest.skip("server does not support hardlink extension")
|
||||
os.unlink(name2)
|
||||
os.unlink(name1)
|
||||
except Exception:
|
||||
cleanup(mount_process, mnt_dir)
|
||||
raise
|
||||
else:
|
||||
umount(mount_process, mnt_dir)
|
||||
|
||||
# Now test with disable_hardlink — links should fail
|
||||
mount_process, mnt_dir, src_dir = _mount_sshfs(tmpdir, ["disable_hardlink"])
|
||||
try:
|
||||
name1 = pjoin(mnt_dir, name_generator())
|
||||
name2 = pjoin(mnt_dir, name_generator())
|
||||
with open(name1, "wb") as fh:
|
||||
fh.write(b"test")
|
||||
with pytest.raises(OSError) as exc_info:
|
||||
os.link(name1, name2)
|
||||
assert exc_info.value.errno in (errno.ENOSYS, errno.EPERM)
|
||||
os.unlink(name1)
|
||||
except Exception:
|
||||
cleanup(mount_process, mnt_dir)
|
||||
raise
|
||||
else:
|
||||
umount(mount_process, mnt_dir)
|
||||
|
||||
|
||||
def test_follow_symlinks(tmpdir, capfd):
|
||||
capfd.register_output(r"^Warning: Permanently added 'localhost' .+", count=0)
|
||||
mount_process, mnt_dir, src_dir = _mount_sshfs(tmpdir, ["follow_symlinks"])
|
||||
try:
|
||||
target_name = name_generator()
|
||||
target = pjoin(src_dir, target_name)
|
||||
with open(target, "wb") as fh:
|
||||
fh.write(b"symlink target data")
|
||||
|
||||
link = pjoin(src_dir, name_generator())
|
||||
os.symlink(target_name, link)
|
||||
|
||||
mnt_link = pjoin(mnt_dir, os.path.basename(link))
|
||||
# With follow_symlinks, stat should return the target's attributes
|
||||
# and the entry should appear as a regular file, not a symlink
|
||||
fstat = os.lstat(mnt_link)
|
||||
assert stat.S_ISREG(fstat.st_mode)
|
||||
with open(mnt_link, "rb") as fh:
|
||||
assert fh.read() == b"symlink target data"
|
||||
|
||||
os.unlink(link)
|
||||
os.unlink(target)
|
||||
except Exception:
|
||||
cleanup(mount_process, mnt_dir)
|
||||
raise
|
||||
else:
|
||||
umount(mount_process, mnt_dir)
|
||||
|
||||
|
||||
def test_direct_io(tmpdir, capfd):
|
||||
capfd.register_output(r"^Warning: Permanently added 'localhost' .+", count=0)
|
||||
mount_process, mnt_dir, src_dir = _mount_sshfs(tmpdir, ["direct_io"])
|
||||
try:
|
||||
name = name_generator()
|
||||
mnt_name = pjoin(mnt_dir, name)
|
||||
src_name = pjoin(src_dir, name)
|
||||
data = b"direct io test data\n" * 100
|
||||
|
||||
with open(mnt_name, "wb") as fh:
|
||||
fh.write(data)
|
||||
with open(mnt_name, "rb") as fh:
|
||||
assert fh.read() == data
|
||||
with open(src_name, "rb") as fh:
|
||||
assert fh.read() == data
|
||||
|
||||
os.unlink(mnt_name)
|
||||
except Exception:
|
||||
cleanup(mount_process, mnt_dir)
|
||||
raise
|
||||
else:
|
||||
umount(mount_process, mnt_dir)
|
||||
|
||||
|
||||
def test_bad_sftp_reply_len(tmpdir):
|
||||
"""sshfs must reject a zero-length SFTP reply instead of underflowing."""
|
||||
helper = tmpdir.join("bad_sftp.py")
|
||||
helper.write(
|
||||
'#!/usr/bin/env python3\n'
|
||||
'import os, struct, sys\n'
|
||||
'def read_pkt():\n'
|
||||
' hdr = os.read(0, 4)\n'
|
||||
' if len(hdr) < 4: sys.exit(0)\n'
|
||||
' n = struct.unpack(">I", hdr)[0]\n'
|
||||
' while n:\n'
|
||||
' c = os.read(0, n)\n'
|
||||
' if not c: sys.exit(0)\n'
|
||||
' n -= len(c)\n'
|
||||
'read_pkt()\n'
|
||||
'os.write(1, struct.pack(">IBI", 5, 2, 3))\n' # SSH_FXP_VERSION v3
|
||||
'read_pkt()\n'
|
||||
'os.write(1, struct.pack(">IB", 0, 0))\n' # len=0 reply (5 bytes on wire)
|
||||
)
|
||||
helper.chmod(0o755)
|
||||
|
||||
mnt_dir = str(tmpdir.mkdir("mnt"))
|
||||
cmdline = base_cmdline + [
|
||||
pjoin(basename, "sshfs"),
|
||||
"-f",
|
||||
"dummy:/",
|
||||
mnt_dir,
|
||||
"-o", f"ssh_command={helper}",
|
||||
]
|
||||
res = subprocess.run(
|
||||
cmdline,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=10,
|
||||
text=True,
|
||||
)
|
||||
assert res.returncode != 0
|
||||
assert "bad reply len: 0" in res.stderr
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Disable leak checking for now, there are some issues (or false positives)
|
||||
# that we still need to fix
|
||||
export ASAN_OPTIONS="detect_leaks=0"
|
||||
|
||||
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
|
||||
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}
|
||||
)
|
||||
done
|
||||
(cd "build-${CC}"; sudo ninja install)
|
||||
|
||||
# Sanitized build
|
||||
CC=clang
|
||||
for san in undefined address; do
|
||||
(
|
||||
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
|
||||
)
|
||||
done
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Install fuse
|
||||
wget https://github.com/libfuse/libfuse/archive/master.zip
|
||||
unzip master.zip
|
||||
cd libfuse-master
|
||||
mkdir build
|
||||
cd build
|
||||
meson ..
|
||||
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/
|
||||
printf '%s\n' /usr/local/lib/*-linux-gnu | sudo tee /etc/ld.so.conf.d/usrlocal.conf
|
||||
sudo ldconfig
|
||||
|
||||
# Setup ssh
|
||||
ssh-keygen -b 1024 -t rsa -f ~/.ssh/id_rsa -P ''
|
||||
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
ssh -o "StrictHostKeyChecking=no" localhost echo "SSH connection succeeded"
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import pytest
|
||||
import os
|
||||
import stat
|
||||
import time
|
||||
from os.path import join as pjoin
|
||||
from contextlib import contextmanager
|
||||
|
||||
basename = pjoin(os.path.dirname(__file__), "..")
|
||||
|
||||
|
||||
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")
|
||||
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,
|
||||
)
|
||||
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])
|
||||
assert not os.path.ismount(mnt_dir)
|
||||
|
||||
# Give mount process a little while to terminate. Popen.wait(timeout)
|
||||
# was only added in 3.3...
|
||||
elapsed = 0
|
||||
while elapsed < 30:
|
||||
code = mount_process.poll()
|
||||
if code is not None:
|
||||
if code == 0:
|
||||
return
|
||||
pytest.fail(f"file system process terminated with code {code}")
|
||||
time.sleep(0.1)
|
||||
elapsed += 0.1
|
||||
pytest.fail("mount process did not terminate")
|
||||
|
||||
|
||||
def safe_sleep(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
|
||||
while now < end:
|
||||
time.sleep(end - now)
|
||||
now = time.time()
|
||||
|
||||
|
||||
def fuse_test_marker():
|
||||
"""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()`.
|
||||
"""
|
||||
|
||||
def skip(reason: str):
|
||||
return pytest.mark.skip(reason=reason)
|
||||
|
||||
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"):
|
||||
return skip("FUSE kernel module does not seem to be loaded")
|
||||
|
||||
if os.getuid() == 0:
|
||||
return pytest.mark.uses_fuse()
|
||||
|
||||
mode = os.stat(fusermount_path).st_mode
|
||||
if mode & stat.S_ISUID == 0:
|
||||
return skip("fusermount executable not setuid, and we are not root.")
|
||||
|
||||
try:
|
||||
fd = os.open("/dev/fuse", os.O_RDWR)
|
||||
except OSError as exc:
|
||||
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", "--"]
|
||||
else:
|
||||
base_cmdline = []
|
||||
@@ -0,0 +1,9 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
fprintf(stderr, "\x1B[31m\e[1m"
|
||||
"This is not the command you are looking for.\n"
|
||||
"You probably want to run 'python3 -m pytest test/' instead"
|
||||
"\e[0m\n");
|
||||
return 1;
|
||||
}
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Don't call this script. It is used internally by the Meson
|
||||
# build system. Thank you for your cooperation.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
bindir="$2"
|
||||
sbindir="$1"
|
||||
prefix="${MESON_INSTALL_DESTDIR_PREFIX}"
|
||||
|
||||
mkdir -p "${prefix}/${sbindir}"
|
||||
|
||||
ln -svf --relative "${prefix}/${bindir}/sshfs" \
|
||||
"${prefix}/${sbindir}/mount.sshfs"
|
||||
|
||||
ln -svf --relative "${prefix}/${bindir}/sshfs" \
|
||||
"${prefix}/${sbindir}/mount.fuse.sshfs"
|
||||
Reference in New Issue
Block a user