Compare commits
300 Commits
sshfs_1_3
...
sshfs-3.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 8999210c93 | |||
| 5ed2cf8a8b | |||
| 9f2eb04371 | |||
| 6c301e043b | |||
| 6d7ce1607a | |||
| 5659d0d2ea | |||
| fe3ce10ff8 | |||
| 132dd88755 | |||
| af1f3c6235 | |||
| 8cc069f666 | |||
| d7cb098f1d | |||
| c7d8500634 | |||
| 2896aed825 | |||
| 110163d8d1 | |||
| d93f137c8f | |||
| 1dbda31786 | |||
| 33667f2a56 | |||
| f627055bc7 | |||
| efeed71295 | |||
| 96c6138978 | |||
| 1afaef8f46 | |||
| e5acfce8ed | |||
| e178150a72 | |||
| c63acf3dd7 | |||
| e3cd445a4e | |||
| 1167c11901 | |||
| a175c2876a | |||
| b2fa759358 | |||
| 1756abe6fd | |||
| f51d7b0c6a | |||
| df10d96ac2 | |||
| a2dda962c4 | |||
| 504e26f938 | |||
| 74bfa3850a | |||
| 0f6f33b1b4 | |||
| a9a1cc0046 | |||
| b5983a0ce8 | |||
| adc55c9d2b | |||
| 451f2075f2 | |||
| 391f676705 | |||
| c1e799b4da | |||
| ebfeebd468 | |||
| d1ddbbcae5 | |||
| e812e29d39 | |||
| 80db43d7dc | |||
| 27e7029148 | |||
| 30e90ae080 | |||
| 9b4ca1aade | |||
| a03d3eab39 | |||
| 665bb97ef6 | |||
| b7e8fee81b | |||
| 869aa42e97 | |||
| d8230e72ca | |||
| e0025219b3 | |||
| 19ab22abec | |||
| 3bf3be3bcd | |||
| ddbe3bee44 | |||
| b10d21db31 | |||
| f7c7713a82 | |||
| 3087690aba | |||
| 3a60def57e | |||
| f5b939d40c | |||
| 9b3fb20112 | |||
| d20b25cd2d | |||
| 055649e2fa | |||
| d18349da3b | |||
| cf10c34573 | |||
| 008cf7bcf4 | |||
| e4e14109ad | |||
| 9bcb989454 | |||
| 48122e8043 | |||
| f2b17c299b | |||
| c3d83ee24b | |||
| 1fc97d3f2c | |||
| b42f84218c | |||
| 6b4415ada4 | |||
| 6a2d06e36f | |||
| 334e9e6a27 | |||
| 91c1f2ba9e | |||
| 990f30d767 | |||
| c1d62f2032 | |||
| 61524cd1e8 | |||
| 9f4ba56b52 | |||
| d04fd55128 | |||
| 1458193728 | |||
| ce6753c88f | |||
| 66458931dd | |||
| f4631f0a17 | |||
| 3c44e22ddd | |||
| b389c66b32 | |||
| f64407d865 | |||
| eb60e2d1a2 | |||
| 0d34c7b742 | |||
| ff32332e83 | |||
| c1284b4089 | |||
| 0120e6b461 | |||
| 1ac41d2ae2 | |||
| ea6fa1d7c9 | |||
| 6a954f429c | |||
| 0e87004ab2 | |||
| e6f7fb4e50 | |||
| 857d634e09 | |||
| 4dc10686e0 | |||
| b3af91b123 | |||
| acb0832ffd | |||
| c5949f06fa | |||
| b0f3d73028 | |||
| a6b6b5c820 | |||
| a306a80b6e | |||
| bb3ea51117 | |||
| 20fafed0a4 | |||
| 69b2b3cc0e | |||
| fa9bfc6d08 | |||
| 90619fe249 | |||
| 04a610bad1 | |||
| d8da0c4ed6 | |||
| 163683d9ef | |||
| f2086ccfbc | |||
| b4023a19dd | |||
| 75bfa9a6c0 | |||
| e8e26c0bc1 | |||
| cfcae008e3 | |||
| 7ca565d80d | |||
| 301cc5218b | |||
| d666318c7a | |||
| ea0d68651e | |||
| 2f6665d84e | |||
| 2ce4069832 | |||
| 97228b866a | |||
| f3286cdf86 | |||
| 955751ad36 | |||
| b5f0c128f5 | |||
| 77d57a4a5d | |||
| 19cb995c23 | |||
| 49203ee923 | |||
| 6c1968e9e5 | |||
| a575d48234 | |||
| eac5268e87 | |||
| 6d5e12e1b6 | |||
| b1b0c4b6f5 | |||
| f018ac3b1c | |||
| 2431a280eb | |||
| 0eabd86300 | |||
| ae4614e690 | |||
| b1fa0408fb | |||
| 09e37d536f | |||
| be44229cfe | |||
| c7dbdc1f77 | |||
| 8a4015a3b6 | |||
| fd52ef3e51 | |||
| 1abdbe64a3 | |||
| 4b28b15c34 | |||
| 1a76a16bd9 | |||
| b1748d87aa | |||
| ff1a5c7a71 | |||
| 09579af980 | |||
| eeea0ff5a6 | |||
| 4b88fc4731 | |||
| b246ad3da0 | |||
| c61e700aab | |||
| 4d6e5a03da | |||
| 4802b14791 | |||
| 03381402b2 | |||
| e83b2e61d1 | |||
| ad9f0cf2a3 | |||
| 501b9f712f | |||
| 37b408459a | |||
| 1af7516f1e | |||
| 15f911853e | |||
| eff1631428 | |||
| 4399cfca84 | |||
| dd258a5117 | |||
| feeaf76340 | |||
| b3ffcfe3ac | |||
| d045a9be5e | |||
| 09d5ee2eee | |||
| b917f9a420 | |||
| 2d2ad5176a | |||
| 006aa04ff4 | |||
| 4046be4487 | |||
| d8c313ff85 | |||
| 666119d285 | |||
| f3e8d58bd7 | |||
| 326ff7f97e | |||
| 7f4eaea507 | |||
| 57fc3f4b6a | |||
| b436754a11 | |||
| e277fe1ca2 | |||
| c9c9e9dfbd | |||
| 3cfb3a6dab | |||
| 790bb8dd36 | |||
| accc218cdb | |||
| 8468900705 | |||
| b60a970602 | |||
| 27c1b7b09d | |||
| 6f0233e11e | |||
| 89da93d07e | |||
| cb4239036a | |||
| 40393a4b0b | |||
| a6e0d2ddd9 | |||
| 1cb9aba6ea | |||
| 3883b4eb0c | |||
| f1f738d5b8 | |||
| d2a3ed327e | |||
| cd6a924cdc | |||
| 258c5ff728 | |||
| 747ffa12f6 | |||
| bad7f96bad | |||
| a162ae9bd4 | |||
| 95116f4b12 | |||
| 3f854ace05 | |||
| 6a6476b52b | |||
| 57a490e12a | |||
| eddbd3967b | |||
| 891a3e5d87 | |||
| 5dd6469a84 | |||
| aa44e01549 | |||
| e916cdd633 | |||
| 0096ea860e | |||
| 1f3cdff4ed | |||
| c3b0185deb | |||
| 7e97377d52 | |||
| adba209812 | |||
| 5377a9a571 |
-16
@@ -1,16 +0,0 @@
|
||||
.deps
|
||||
COPYING
|
||||
INSTALL
|
||||
Makefile
|
||||
Makefile.in
|
||||
aclocal.m4
|
||||
*.cache
|
||||
config.*
|
||||
configure
|
||||
compile
|
||||
depcomp
|
||||
install-sh
|
||||
missing
|
||||
sshfs
|
||||
stamp*
|
||||
.deps
|
||||
@@ -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))))))
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#
|
||||
# NOTE! Don't add files that are generated in specific
|
||||
# subdirectories here. Add them in the ".gitignore" file
|
||||
# in that subdirectory instead.
|
||||
#
|
||||
# NOTE! Please use 'git ls-files -i --exclude-standard'
|
||||
# command after changing this file, to see if there are
|
||||
# any tracked files which get ignored after the change.
|
||||
*.o
|
||||
*.lo
|
||||
*.la
|
||||
*.gz
|
||||
\#*#
|
||||
*.orig
|
||||
*~
|
||||
Makefile.in
|
||||
Makefile
|
||||
*.m4
|
||||
stamp-h*
|
||||
config.*
|
||||
sshfs.1
|
||||
/sshfs
|
||||
/ltmain.sh
|
||||
/configure
|
||||
/install-sh
|
||||
/mkinstalldirs
|
||||
/missing
|
||||
/*.cache
|
||||
/depcomp
|
||||
/compile
|
||||
/libtool
|
||||
/INSTALL
|
||||
/.pc
|
||||
/patches
|
||||
/m4
|
||||
.deps/
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
language:
|
||||
- c
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- valgrind
|
||||
- clang
|
||||
- gcc
|
||||
- gcc-6
|
||||
- python-docutils
|
||||
- python3-pip
|
||||
install: test/travis-install.sh
|
||||
script: test/travis-build.sh
|
||||
|
||||
@@ -1 +1,45 @@
|
||||
Miklos Szeredi <miklos@szeredi.hu>
|
||||
Current Maintainer
|
||||
------------------
|
||||
|
||||
Nikolaus Rath <Nikolaus@rath.org>
|
||||
|
||||
|
||||
Past Maintainers
|
||||
----------------
|
||||
|
||||
Miklos Szeredi <miklos@szeredi.hu> (until 12/2015)
|
||||
|
||||
|
||||
Contributors (autogenerated list)
|
||||
---------------------------------
|
||||
|
||||
Alan Jenkins <alan.christopher.jenkins@gmail.com>
|
||||
Alexander Neumann <alexander@bumpern.de>
|
||||
Anatol Pomozov <anatol.pomozov@gmail.com>
|
||||
Benjamin Fleischer <fleiben@gmail.com>
|
||||
Berserker <berserker.troll@yandex.com>
|
||||
Bill Zissimopoulos <billziss@navimatics.com>
|
||||
Brandon Carter <b-carter@users.noreply.github.com>
|
||||
Chris Wolfe <cwolfe@chromium.org>
|
||||
Daniel Lublin <daniel@lublin.se>
|
||||
gala <gala132@users.noreply.github.com>
|
||||
George Vlahavas <vlahavas@gmail.com>
|
||||
G.raud Meyer <graud@gmx.com>
|
||||
harrim4n <git@harrim4n.com>
|
||||
Jakub Jelen <jjelen@redhat.com>
|
||||
Josh Triplett <josh@joshtriplett.org>
|
||||
Julio Merino <jmmv@google.com>
|
||||
Julio Merino <jmmv@meroh.net>
|
||||
Mike Kelly <mike@pair.com>
|
||||
Miklos Szeredi <miklos@szeredi.hu>
|
||||
Miklos Szeredi <mszeredi@suse.cz>
|
||||
Nikolaus Rath <Nikolaus@rath.org>
|
||||
Percy Jahn <email@percyjahn.de>
|
||||
Qais Patankar <qaisjp@gmail.com>
|
||||
Reid Wagner <wagnerreid@gmail.com>
|
||||
Rian Hunter <rian@alum.mit.edu>
|
||||
Rian Hunter <rianhunter@users.noreply.github.com>
|
||||
Samuel Murray <samuel.murray@outlook.com>
|
||||
Timo Savola <timo.savola@iki.fi>
|
||||
tpoindessous <thomas@poindessous.com>
|
||||
Zoltan Kuscsik <zoltan.kuscsik@linaro.org>
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -1,139 +0,0 @@
|
||||
2005-10-29 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Released 1.3
|
||||
|
||||
2005-10-28 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Add atomic create+open and ftruncate operation. This should fix
|
||||
issues with 'cp' and other programs failing with "Permission
|
||||
denied". To be effective, needs FUSE version 2.5 and kernel
|
||||
version 2.6.15 (just a guess, since neither of them is released
|
||||
yet).
|
||||
|
||||
2005-10-27 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Add support for SSH protocol version 1. Bug reported by Miklos
|
||||
Bagi Jr.
|
||||
|
||||
2005-10-26 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Fix typo (ConnectTimeout -> ConnectionTimeout) in ssh options.
|
||||
Bug reported by Miklos Bagi Jr.
|
||||
|
||||
2005-10-19 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* GNOME Nautilus fails to copy file to sshfs filesystem, because
|
||||
FUSE returns zero free space. So instead return huge (999999999
|
||||
kbytes) amount of free space, yet it should be obvious that the
|
||||
number is artificial. Bug report by Peter Kronheimer
|
||||
|
||||
2005-10-18 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Add remote uid detection and translation ('idmap=user' option).
|
||||
Idea and implementation details worked out by Jean-Marc Valin
|
||||
|
||||
2005-10-17 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Add one more missing lock.
|
||||
|
||||
* Add workaround for failure to rename to an existing file. Based
|
||||
on patch by Michael Best
|
||||
|
||||
2005-10-15 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Protect request ID allocation with mutex. Bug report by Tvrtko
|
||||
Ursulin
|
||||
|
||||
2005-08-17 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Try to calculate approximate disk usage of files from their
|
||||
size, so that 'du' returns meaningful results. Problem reported
|
||||
by Bernd Amend.
|
||||
|
||||
2005-08-15 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Released 1.2
|
||||
|
||||
2005-08-13 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Add 'reconnect' option, which tries to reconnect to the server
|
||||
when the connection is broken. If a password is required for
|
||||
connection, it is recommended that you install ssh-askpass, and
|
||||
set the SSH_ASKPASS environment variable (see 'man ssh' for more
|
||||
details).
|
||||
|
||||
2005-05-05 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Work around missing truncate() support in some older sftp
|
||||
servers (only works for zero size truncate). Thanks to Eduard
|
||||
Czimbalmos for the bug report and help with testing
|
||||
|
||||
2005-04-13 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Fix compilation with gcc-2.95. Reported by David A. Gershman
|
||||
|
||||
2005-03-08 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Make it work on server version 2 (e.g. Sun_SSH_1.0.1). Report
|
||||
and testing by Pieter J. Kersten
|
||||
|
||||
2005-03-04 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Released 1.1
|
||||
|
||||
2005-03-03 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Slightly optimize readahead. Still not clever enough to always
|
||||
keep the pipe filled.
|
||||
|
||||
* Add 'sshfs_debug' option
|
||||
|
||||
2005-02-17 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Parse 'max_read' mount option and if smaller than 65536 forward
|
||||
to FUSE
|
||||
|
||||
2005-02-16 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Added simple readahead (big performance gain in case of
|
||||
sequential read pattern). Can be disabled with '-o no_readahead'
|
||||
|
||||
2005-02-14 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Added asynchronous writeback (big performance gain) and made
|
||||
this the default. Can be disabled with '-o sshfs_sync'
|
||||
|
||||
2005-02-09 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Added option to start arbitary command instead of 'ssh'
|
||||
|
||||
* Re-added '-p PORT' as a convenience option, also '-C' works as
|
||||
in ssh.
|
||||
|
||||
2005-02-08 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Add caching of symlinks
|
||||
|
||||
* Add support for many ssh options to be passed to ssh
|
||||
|
||||
* Port number can now actually be specified with "-o port=PORT",
|
||||
bug spotted by Andrew Ukrainec
|
||||
|
||||
2005-02-07 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Separate attribute caching to a separate layer
|
||||
|
||||
* Add caching of directory contents
|
||||
|
||||
2005-02-03 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
Fix PKG_CONFIG_PATH setting in configure.ac (reported by Alpar
|
||||
Juttner)
|
||||
|
||||
2005-01-09 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Released 1.0
|
||||
|
||||
2004-12-04 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
* Started ChangeLog
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
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,5 +0,0 @@
|
||||
## Process this file with automake to produce Makefile.in
|
||||
|
||||
bin_PROGRAMS = sshfs
|
||||
sshfs_SOURCES = sshfs.c cache.c opts.c cache.h opts.h
|
||||
sshfs_CPPFLAGS = -DFUSE_USE_VERSION=22
|
||||
@@ -1,39 +0,0 @@
|
||||
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,73 +0,0 @@
|
||||
Abstract
|
||||
========
|
||||
|
||||
This is a filesystem client based on the SSH File Transfer Protocol.
|
||||
Since most SSH servers already support this protocol it is very easy
|
||||
to set up: i.e. on the server side there's nothing to do. On the
|
||||
client side mounting the filesystem is as easy as logging into the
|
||||
server with ssh.
|
||||
|
||||
The idea of sshfs was taken from the SSHFS filesystem distributed with
|
||||
LUFS, which I found very useful. There were some limitations of that
|
||||
codebase, so I rewrote it. Features of this implementation are:
|
||||
|
||||
- Based on FUSE (the best userspace filesystem framework for Linux ;)
|
||||
|
||||
- Multithreading: more than one request can be on it's way to the
|
||||
server
|
||||
|
||||
- Allowing large reads (max 64k)
|
||||
|
||||
- Caching directory contents
|
||||
|
||||
- Reconnect on failure
|
||||
|
||||
|
||||
How to mount a filesystem
|
||||
=========================
|
||||
|
||||
Once sshfs is installed (see next section) running it is very simple:
|
||||
|
||||
sshfs hostname: mountpoint
|
||||
|
||||
Note, that it's recommended to run it as user, not as root. For this
|
||||
to work the mountpoint must be owned by the user. If the username is
|
||||
different on the host you are connecting to, then use the
|
||||
"username@host:" form. If you need to enter a password sshfs will ask
|
||||
for it (actually it just runs ssh which ask for the password if
|
||||
needed). You can also specify a directory after the ":". The default
|
||||
is the home directory.
|
||||
|
||||
Also many ssh options can be specified (see the manual pages for
|
||||
sftp(1) and ssh_config(5)), including the remote port number
|
||||
('-oport=PORT')
|
||||
|
||||
To unmount the filesystem:
|
||||
|
||||
fusermount -u moutpoint
|
||||
|
||||
|
||||
Installing
|
||||
==========
|
||||
|
||||
First you need to download FUSE 2.2 or later from:
|
||||
|
||||
http://fuse.sourceforge.net
|
||||
|
||||
You also need to install the devel package for glib2.0. After
|
||||
installing FUSE, compile sshfs the usual way:
|
||||
|
||||
./configure
|
||||
make
|
||||
make install (as root)
|
||||
|
||||
And you are ready to go.
|
||||
|
||||
|
||||
Bugs and feature requests
|
||||
=========================
|
||||
|
||||
Send bug reports to <miklos@szeredi.hu>.
|
||||
|
||||
Good luck!
|
||||
Miklos Szeredi
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
SSHFS
|
||||
=====
|
||||
|
||||
|
||||
About
|
||||
-----
|
||||
|
||||
SSHFS allows you to mount a remote filesystem using SFTP. Most SSH
|
||||
servers support and enable this SFTP access by default, so SSHFS is
|
||||
very simple to use - there's nothing to do on the server-side.
|
||||
|
||||
|
||||
How to use
|
||||
----------
|
||||
|
||||
Once sshfs is installed (see next section) running it is very simple::
|
||||
|
||||
sshfs [user@]hostname:[directory] mountpoint
|
||||
|
||||
It is recommended to run SSHFS as regular user (not as root). For
|
||||
this to work the mountpoint must be owned by the user. If username is
|
||||
omitted SSHFS will use the local username. If the directory is
|
||||
omitted, SSHFS will mount the (remote) home directory. If you need to
|
||||
enter a password sshfs will ask for it (actually it just runs ssh
|
||||
which ask for the password if needed).
|
||||
|
||||
Also many ssh options can be specified (see the manual pages for
|
||||
*sftp(1)* and *ssh_config(5)*), including the remote port number
|
||||
(``-oport=PORT``)
|
||||
|
||||
To unmount the filesystem::
|
||||
|
||||
fusermount -u mountpoint
|
||||
|
||||
On BSD and macOS, to unmount the filesystem::
|
||||
|
||||
umount mountpoint
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
First, download the latest SSHFS release from
|
||||
https://github.com/libfuse/sshfs/releases. On Linux and BSD, you will
|
||||
also need to install libfuse_ 3.1.0 or newer. On macOS, you need
|
||||
OSXFUSE_ instead. Finally, you need the Glib_ library with development
|
||||
headers (which should be available from your operating system's
|
||||
package manager).
|
||||
|
||||
To build and install, we recommend to use Meson_ (version 0.38 or
|
||||
newer) and Ninja_. After extracting the sshfs tarball, create a
|
||||
(temporary) build directory and run Meson::
|
||||
|
||||
$ mkdir build; cd build
|
||||
$ meson ..
|
||||
|
||||
Normally, the default build options will work fine. If you
|
||||
nevertheless want to adjust them, you can do so with the *mesonconf*
|
||||
command::
|
||||
|
||||
$ mesonconf # list options
|
||||
$ mesonconf -D strip=true # set an option
|
||||
|
||||
To build, test and install SSHFS, you then use Ninja (running the
|
||||
tests requires the `py.test`_ Python module)::
|
||||
|
||||
$ ninja
|
||||
$ python3 -m pytest test/ # optional, but recommended
|
||||
$ sudo ninja install
|
||||
|
||||
.. _libfuse: http://github.com/libfuse/libfuse
|
||||
.. _OSXFUSE: https://osxfuse.github.io/
|
||||
.. _Glib: https://developer.gnome.org/glib/stable/
|
||||
.. _Meson: http://mesonbuild.com/
|
||||
.. _Ninja: https://ninja-build.org/
|
||||
.. _`py.test`: http://www.pytest.org/
|
||||
|
||||
Getting Help
|
||||
------------
|
||||
|
||||
If you need help, please ask on the <fuse-sshfs@lists.sourceforge.net>
|
||||
mailing list (subscribe at
|
||||
https://lists.sourceforge.net/lists/listinfo/fuse-sshfs).
|
||||
|
||||
Please report any bugs on the GitHub issue tracker at
|
||||
https://github.com/libfuse/libfuse/issues.
|
||||
|
||||
|
||||
Professional Support
|
||||
--------------------
|
||||
|
||||
Professional support is offered via `Rath Consulting`_.
|
||||
|
||||
.. _`Rath Consulting`: http://www.rath-consulting.biz
|
||||
@@ -7,20 +7,10 @@
|
||||
*/
|
||||
|
||||
#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);
|
||||
int cache_parse_options(int *argcp, char *argv[]);
|
||||
struct fuse_operations *cache_wrap(struct fuse_operations *oper);
|
||||
int cache_parse_options(struct fuse_args *args);
|
||||
void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr);
|
||||
void cache_invalidate(const char *path);
|
||||
uint64_t cache_get_write_ctr(void);
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2008 Amit Singh/Google Inc.
|
||||
* Copyright (c) 2012 Anatol Pomozov
|
||||
* Copyright (c) 2011-2013 Benjamin Fleischer
|
||||
*/
|
||||
|
||||
#include "darwin_compat.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/*
|
||||
* Semaphore implementation based on:
|
||||
*
|
||||
* Copyright (C) 2000,02 Free Software Foundation, Inc.
|
||||
* This file is part of the GNU C Library.
|
||||
* Written by Ga<EB>l Le Mignot <address@hidden>
|
||||
*
|
||||
* The GNU C Library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The GNU C Library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with the GNU C Library; see the file COPYING.LIB. If not,
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
/* Semaphores */
|
||||
|
||||
#define __SEM_ID_NONE ((int)0x0)
|
||||
#define __SEM_ID_LOCAL ((int)0xcafef00d)
|
||||
|
||||
/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_init.html */
|
||||
int
|
||||
darwin_sem_init(darwin_sem_t *sem, int pshared, unsigned int value)
|
||||
{
|
||||
if (pshared) {
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sem->id = __SEM_ID_NONE;
|
||||
|
||||
if (pthread_cond_init(&sem->__data.local.count_cond, NULL)) {
|
||||
goto cond_init_fail;
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&sem->__data.local.count_lock, NULL)) {
|
||||
goto mutex_init_fail;
|
||||
}
|
||||
|
||||
sem->__data.local.count = value;
|
||||
sem->id = __SEM_ID_LOCAL;
|
||||
|
||||
return 0;
|
||||
|
||||
mutex_init_fail:
|
||||
|
||||
pthread_cond_destroy(&sem->__data.local.count_cond);
|
||||
|
||||
cond_init_fail:
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_destroy.html */
|
||||
int
|
||||
darwin_sem_destroy(darwin_sem_t *sem)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
pthread_mutex_lock(&sem->__data.local.count_lock);
|
||||
|
||||
sem->id = __SEM_ID_NONE;
|
||||
pthread_cond_broadcast(&sem->__data.local.count_cond);
|
||||
|
||||
if (pthread_cond_destroy(&sem->__data.local.count_cond)) {
|
||||
res = -1;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&sem->__data.local.count_lock);
|
||||
|
||||
if (pthread_mutex_destroy(&sem->__data.local.count_lock)) {
|
||||
res = -1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int
|
||||
darwin_sem_getvalue(darwin_sem_t *sem, unsigned int *sval)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
pthread_mutex_lock(&sem->__data.local.count_lock);
|
||||
|
||||
if (sem->id != __SEM_ID_LOCAL) {
|
||||
res = -1;
|
||||
errno = EINVAL;
|
||||
} else {
|
||||
*sval = sem->__data.local.count;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&sem->__data.local.count_lock);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_post.html */
|
||||
int
|
||||
darwin_sem_post(darwin_sem_t *sem)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
pthread_mutex_lock(&sem->__data.local.count_lock);
|
||||
|
||||
if (sem->id != __SEM_ID_LOCAL) {
|
||||
res = -1;
|
||||
errno = EINVAL;
|
||||
} else if (sem->__data.local.count < DARWIN_SEM_VALUE_MAX) {
|
||||
sem->__data.local.count++;
|
||||
if (sem->__data.local.count == 1) {
|
||||
pthread_cond_signal(&sem->__data.local.count_cond);
|
||||
}
|
||||
} else {
|
||||
errno = ERANGE;
|
||||
res = -1;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&sem->__data.local.count_lock);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* http://www.opengroup.org/onlinepubs/009695399/functions/sem_timedwait.html */
|
||||
int
|
||||
darwin_sem_timedwait(darwin_sem_t *sem, const struct timespec *abs_timeout)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
if (abs_timeout &&
|
||||
(abs_timeout->tv_nsec < 0 || abs_timeout->tv_nsec >= 1000000000)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_cleanup_push((void(*)(void*))&pthread_mutex_unlock,
|
||||
&sem->__data.local.count_lock);
|
||||
|
||||
pthread_mutex_lock(&sem->__data.local.count_lock);
|
||||
|
||||
if (sem->id != __SEM_ID_LOCAL) {
|
||||
errno = EINVAL;
|
||||
res = -1;
|
||||
} else {
|
||||
if (!sem->__data.local.count) {
|
||||
res = pthread_cond_timedwait(&sem->__data.local.count_cond,
|
||||
&sem->__data.local.count_lock,
|
||||
abs_timeout);
|
||||
}
|
||||
if (res) {
|
||||
assert(res == ETIMEDOUT);
|
||||
res = -1;
|
||||
errno = ETIMEDOUT;
|
||||
} else if (sem->id != __SEM_ID_LOCAL) {
|
||||
res = -1;
|
||||
errno = EINVAL;
|
||||
} else {
|
||||
sem->__data.local.count--;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_cleanup_pop(1);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_trywait.html */
|
||||
int
|
||||
darwin_sem_trywait(darwin_sem_t *sem)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
pthread_mutex_lock(&sem->__data.local.count_lock);
|
||||
|
||||
if (sem->id != __SEM_ID_LOCAL) {
|
||||
res = -1;
|
||||
errno = EINVAL;
|
||||
} else if (sem->__data.local.count) {
|
||||
sem->__data.local.count--;
|
||||
} else {
|
||||
res = -1;
|
||||
errno = EAGAIN;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock (&sem->__data.local.count_lock);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_wait.html */
|
||||
int
|
||||
darwin_sem_wait(darwin_sem_t *sem)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
pthread_cleanup_push((void(*)(void*))&pthread_mutex_unlock,
|
||||
&sem->__data.local.count_lock);
|
||||
|
||||
pthread_mutex_lock(&sem->__data.local.count_lock);
|
||||
|
||||
if (sem->id != __SEM_ID_LOCAL) {
|
||||
errno = EINVAL;
|
||||
res = -1;
|
||||
} else {
|
||||
if (!sem->__data.local.count) {
|
||||
pthread_cond_wait(&sem->__data.local.count_cond,
|
||||
&sem->__data.local.count_lock);
|
||||
if (!sem->__data.local.count) {
|
||||
/* spurious wakeup, assume it is an interruption */
|
||||
res = -1;
|
||||
errno = EINTR;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (sem->id != __SEM_ID_LOCAL) {
|
||||
res = -1;
|
||||
errno = EINVAL;
|
||||
} else {
|
||||
sem->__data.local.count--;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
pthread_cleanup_pop(1);
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2008 Amit Singh/Google Inc.
|
||||
* Copyright (c) 2011-2013 Benjamin Fleischer
|
||||
*/
|
||||
|
||||
#ifndef _DARWIN_COMPAT_
|
||||
#define _DARWIN_COMPAT_
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
/* Semaphores */
|
||||
|
||||
typedef struct darwin_sem {
|
||||
int id;
|
||||
union {
|
||||
struct
|
||||
{
|
||||
unsigned int count;
|
||||
pthread_mutex_t count_lock;
|
||||
pthread_cond_t count_cond;
|
||||
} local;
|
||||
} __data;
|
||||
} darwin_sem_t;
|
||||
|
||||
#define DARWIN_SEM_VALUE_MAX ((int32_t)32767)
|
||||
|
||||
int darwin_sem_init(darwin_sem_t *sem, int pshared, unsigned int value);
|
||||
int darwin_sem_destroy(darwin_sem_t *sem);
|
||||
int darwin_sem_getvalue(darwin_sem_t *sem, unsigned int *value);
|
||||
int darwin_sem_post(darwin_sem_t *sem);
|
||||
int darwin_sem_timedwait(darwin_sem_t *sem, const struct timespec *abs_timeout);
|
||||
int darwin_sem_trywait(darwin_sem_t *sem);
|
||||
int darwin_sem_wait(darwin_sem_t *sem);
|
||||
|
||||
/* Caller must not include <semaphore.h> */
|
||||
|
||||
typedef darwin_sem_t sem_t;
|
||||
|
||||
#define sem_init(s, p, v) darwin_sem_init(s, p, v)
|
||||
#define sem_destroy(s) darwin_sem_destroy(s)
|
||||
#define sem_getvalue(s, v) darwin_sem_getvalue(s, v)
|
||||
#define sem_post(s) darwin_sem_post(s)
|
||||
#define sem_timedwait(s, t) darwin_sem_timedwait(s, t)
|
||||
#define sem_trywait(s) darwin_sem_trywait(s)
|
||||
#define sem_wait(s) darwin_sem_wait(s)
|
||||
|
||||
#define SEM_VALUE_MAX DARWIN_SEM_VALUE_MAX
|
||||
|
||||
#endif /* _DARWIN_COMPAT_ */
|
||||
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
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,12 +0,0 @@
|
||||
AC_INIT(sshfs-fuse, 1.3)
|
||||
AM_INIT_AUTOMAKE
|
||||
AM_CONFIG_HEADER(config.h)
|
||||
|
||||
AC_PROG_CC
|
||||
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
|
||||
PKG_CHECK_MODULES(SSHFS, [fuse >= 2.2 glib-2.0])
|
||||
CFLAGS="$CFLAGS -Wall -W -D_REENTRANT $SSHFS_CFLAGS"
|
||||
LIBS="$SSHFS_LIBS"
|
||||
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
AC_OUTPUT
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/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
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
project('sshfs', 'c', version: '3.4.0',
|
||||
meson_version: '>= 0.38',
|
||||
default_options: [ 'buildtype=debugoptimized' ])
|
||||
|
||||
add_global_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H',
|
||||
'-Wall', '-Wextra', '-Wno-sign-compare',
|
||||
'-Wmissing-declarations', '-Wwrite-strings',
|
||||
language: 'c')
|
||||
|
||||
# Some (stupid) GCC versions warn about unused return values even when they are
|
||||
# casted to void. This makes -Wunused-result pretty useless, since there is no
|
||||
# way to suppress the warning when we really *want* to ignore the value.
|
||||
cc = meson.get_compiler('c')
|
||||
code = '''
|
||||
__attribute__((warn_unused_result)) int get_4() {
|
||||
return 4;
|
||||
}
|
||||
int main(void) {
|
||||
(void) get_4();
|
||||
return 0;
|
||||
}'''
|
||||
if not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ])
|
||||
message('Compiler warns about unused result even when casting to void')
|
||||
add_global_arguments('-Wno-unused-result', language: 'c')
|
||||
endif
|
||||
|
||||
|
||||
rst2man = find_program('rst2man', required: false)
|
||||
|
||||
cfg = configuration_data()
|
||||
|
||||
cfg.set_quoted('PACKAGE_VERSION', meson.project_version())
|
||||
|
||||
include_dirs = [ include_directories('.') ]
|
||||
sshfs_sources = ['sshfs.c', 'cache.c']
|
||||
if target_machine.system() == 'darwin'
|
||||
cfg.set_quoted('IDMAP_DEFAULT', 'user')
|
||||
sshfs_sources += [ 'compat/fuse_opt.c', 'compat/darwin_compat.c' ]
|
||||
include_dirs += [ include_directories('compat') ]
|
||||
else
|
||||
cfg.set_quoted('IDMAP_DEFAULT', 'none')
|
||||
endif
|
||||
|
||||
configure_file(output: 'config.h',
|
||||
configuration : cfg)
|
||||
|
||||
sshfs_deps = [ dependency('fuse3', version: '>= 3.1.0'),
|
||||
dependency('glib-2.0'),
|
||||
dependency('gthread-2.0') ]
|
||||
|
||||
executable('sshfs', sshfs_sources,
|
||||
include_directories: include_dirs,
|
||||
dependencies: sshfs_deps,
|
||||
c_args: ['-DFUSE_USE_VERSION=31'],
|
||||
install: true,
|
||||
install_dir: get_option('bindir'))
|
||||
|
||||
if rst2man.found()
|
||||
custom_target('manpages', input: [ 'sshfs.rst' ], output: [ 'sshfs.1' ],
|
||||
command: [rst2man, '@INPUT@', '@OUTPUT@'], install: true,
|
||||
install_dir: join_paths(get_option('mandir'), 'man1'))
|
||||
else
|
||||
message('rst2man not found, not building manual page.')
|
||||
endif
|
||||
|
||||
meson.add_install_script('utils/install_helper.sh',
|
||||
get_option('sbindir'),
|
||||
get_option('bindir'))
|
||||
|
||||
|
||||
subdir('test')
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
Mount option parsing
|
||||
Copyright (C) 2004 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include "opts.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <glib.h>
|
||||
|
||||
static int process_option(char *arg, struct opt opts[], int case_sensitive)
|
||||
{
|
||||
int i;
|
||||
char *eq = strchr(arg, '=');
|
||||
if (eq)
|
||||
*eq = '\0';
|
||||
|
||||
for (i = 0; opts[i].optname != NULL; i++) {
|
||||
if (case_sensitive) {
|
||||
if (strcmp(opts[i].optname, arg) == 0)
|
||||
break;
|
||||
} else if (strcasecmp(opts[i].optname, arg) == 0)
|
||||
break;
|
||||
}
|
||||
if (opts[i].optname == NULL) {
|
||||
if (eq)
|
||||
*eq = '=';
|
||||
return 0;
|
||||
}
|
||||
opts[i].present = 1;
|
||||
if (eq) {
|
||||
if (opts[i].value)
|
||||
g_free(opts[i].value);
|
||||
opts[i].value = g_strdup(eq+1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int process_option_group(char *arg, struct opt opts[],
|
||||
int case_sensitive)
|
||||
{
|
||||
int remove = 1;
|
||||
char *prevcomma = NULL;
|
||||
while (1) {
|
||||
int remove_one;
|
||||
char *comma = strchr(arg, ',');
|
||||
if (comma)
|
||||
*comma = '\0';
|
||||
remove_one = process_option(arg, opts, case_sensitive);
|
||||
if (remove_one) {
|
||||
if (comma)
|
||||
memmove(arg, comma + 1, strlen(comma + 1) + 1);
|
||||
} else {
|
||||
remove = 0;
|
||||
if (comma)
|
||||
arg = comma + 1;
|
||||
}
|
||||
if (!remove_one && prevcomma)
|
||||
*prevcomma = ',';
|
||||
if (!comma)
|
||||
break;
|
||||
prevcomma = comma;
|
||||
}
|
||||
return remove;
|
||||
}
|
||||
|
||||
void process_options(int *argcp, char *argv[], struct opt opts[],
|
||||
int case_sensitive)
|
||||
{
|
||||
int argctr;
|
||||
int newargctr;
|
||||
|
||||
for (argctr = 1, newargctr = 1; argctr < *argcp; argctr++) {
|
||||
char *arg = argv[argctr];
|
||||
int removed = 0;
|
||||
if (arg[0] == '-' && arg[1] == 'o') {
|
||||
if (arg[2])
|
||||
removed = process_option_group(arg+2, opts, case_sensitive);
|
||||
else {
|
||||
if (argctr + 1 < *argcp) {
|
||||
argctr++;
|
||||
arg = argv[argctr];
|
||||
removed = process_option_group(arg, opts, case_sensitive);
|
||||
if (removed)
|
||||
g_free(argv[argctr-1]);
|
||||
else if (argctr != newargctr)
|
||||
argv[newargctr++] = argv[argctr-1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removed)
|
||||
g_free(arg);
|
||||
else {
|
||||
if(argctr != newargctr)
|
||||
argv[newargctr] = arg;
|
||||
newargctr++;
|
||||
}
|
||||
}
|
||||
*argcp = newargctr;
|
||||
}
|
||||
|
||||
int opt_get_unsigned(const struct opt *o, unsigned *valp)
|
||||
{
|
||||
char *end;
|
||||
unsigned val;
|
||||
if (!o->value || !o->value[0]) {
|
||||
fprintf(stderr, "Missing value for '%s' option\n", o->optname);
|
||||
return -1;
|
||||
}
|
||||
val = strtoul(o->value, &end, 0);
|
||||
if (end[0]) {
|
||||
fprintf(stderr, "Invalid value for '%s' option\n", o->optname);
|
||||
return -1;
|
||||
}
|
||||
*valp = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *opt_get_string(const struct opt *o)
|
||||
{
|
||||
if (!o->value || !o->value[0]) {
|
||||
fprintf(stderr, "Missing value for '%s' option\n", o->optname);
|
||||
return NULL;
|
||||
}
|
||||
return o->value;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
Mount option parsing
|
||||
Copyright (C) 2004 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
struct opt {
|
||||
const char *optname;
|
||||
int present;
|
||||
char *value;
|
||||
};
|
||||
|
||||
void process_options(int *argcp, char *argv[], struct opt opts[],
|
||||
int case_sensitive);
|
||||
|
||||
int opt_get_unsigned(const struct opt *o, unsigned *valp);
|
||||
|
||||
char *opt_get_string(const struct opt *o);
|
||||
@@ -0,0 +1,288 @@
|
||||
=======
|
||||
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.
|
||||
|
||||
SSHFS uses FUSE (Filesystem in Userspace) and should work on any
|
||||
operating system that provides a FUSE implementation. Currently,
|
||||
this includes Linux, FreeBSD and Mac OS X.
|
||||
|
||||
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 slave
|
||||
communicate over stdin and stdout bypassing network
|
||||
|
||||
-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.
|
||||
|
||||
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
|
||||
~~~~~~~~~~~
|
||||
|
||||
In some cases, attempts to access the SSHFS mountpoint may freeze if
|
||||
no filesystem activity has occured 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.
|
||||
|
||||
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__/
|
||||
@@ -0,0 +1,89 @@
|
||||
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.mark.hookwrapper
|
||||
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.yield_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)
|
||||
@@ -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,2 @@
|
||||
[pytest]
|
||||
addopts = --verbose --assert=rewrite --tb=native -x -r a
|
||||
Executable
+444
@@ -0,0 +1,444 @@
|
||||
#!/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 contextlib import contextmanager
|
||||
from tempfile import NamedTemporaryFile
|
||||
from util import (wait_for_mount, umount, cleanup, base_cmdline,
|
||||
basename, fuse_test_marker, safe_sleep)
|
||||
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]):
|
||||
__ctr[0] += 1
|
||||
return 'testfile_%d' % __ctr[0]
|
||||
|
||||
@pytest.mark.parametrize("debug", (False, True))
|
||||
@pytest.mark.parametrize("cache_timeout", (0,1))
|
||||
@pytest.mark.parametrize("sync_rd", (True, False))
|
||||
def test_sshfs(tmpdir, debug, cache_timeout, sync_rd, capfd):
|
||||
|
||||
# Avoid false positives from debug messages
|
||||
#if debug:
|
||||
# capfd.register_output(r'^ unique: [0-9]+, error: -[0-9]+ .+$',
|
||||
# count=0)
|
||||
|
||||
# Test if we can ssh into localhost without password
|
||||
try:
|
||||
res = subprocess.call(['ssh', '-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', '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', 'dcache_timeout=%d' % cache_timeout,
|
||||
'-o', 'dir_cache=yes' ]
|
||||
|
||||
# FUSE Cache
|
||||
cmdline += [ '-o', 'entry_timeout=0',
|
||||
'-o', 'attr_timeout=0' ]
|
||||
|
||||
|
||||
new_env = dict(os.environ) # copy, don't modify
|
||||
|
||||
# Abort on warnings from glib
|
||||
new_env['G_DEBUG'] = 'fatal-warnings'
|
||||
|
||||
mount_process = subprocess.Popen(cmdline, env=new_env)
|
||||
try:
|
||||
wait_for_mount(mount_process, mnt_dir)
|
||||
|
||||
tst_statvfs(mnt_dir)
|
||||
tst_readdir(src_dir, mnt_dir)
|
||||
tst_open_read(src_dir, mnt_dir)
|
||||
tst_open_write(src_dir, mnt_dir)
|
||||
tst_append(src_dir, mnt_dir)
|
||||
tst_seek(src_dir, mnt_dir)
|
||||
tst_create(mnt_dir)
|
||||
tst_passthrough(src_dir, mnt_dir, cache_timeout)
|
||||
tst_mkdir(mnt_dir)
|
||||
tst_rmdir(src_dir, mnt_dir, cache_timeout)
|
||||
tst_unlink(src_dir, mnt_dir, cache_timeout)
|
||||
tst_symlink(mnt_dir)
|
||||
if os.getuid() == 0:
|
||||
tst_chown(mnt_dir)
|
||||
|
||||
# SSHFS only supports one second resolution when setting
|
||||
# file timestamps.
|
||||
tst_utimens(mnt_dir, tol=1)
|
||||
tst_utimens_now(mnt_dir)
|
||||
|
||||
tst_link(mnt_dir, cache_timeout)
|
||||
tst_truncate_path(mnt_dir)
|
||||
tst_truncate_fd(mnt_dir)
|
||||
tst_open_unlink(mnt_dir)
|
||||
except:
|
||||
cleanup(mnt_dir)
|
||||
raise
|
||||
else:
|
||||
umount(mount_process, mnt_dir)
|
||||
|
||||
@contextmanager
|
||||
def os_open(name, flags):
|
||||
fd = os.open(name, flags)
|
||||
try:
|
||||
yield fd
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
def os_create(name):
|
||||
os.close(os.open(name, os.O_CREAT | os.O_RDWR))
|
||||
|
||||
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_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)
|
||||
|
||||
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(mnt_dir):
|
||||
os.statvfs(mnt_dir)
|
||||
|
||||
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)
|
||||
|
||||
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]
|
||||
|
||||
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]
|
||||
|
||||
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(src_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)
|
||||
assert os.stat(src_name) == os.stat(mnt_name)
|
||||
|
||||
name = name_generator()
|
||||
src_name = pjoin(src_dir, name)
|
||||
mnt_name = pjoin(src_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)
|
||||
assert os.stat(src_name) == os.stat(mnt_name)
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/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 gcc-6 clang; do
|
||||
mkdir build-${CC}; cd build-${CC}
|
||||
if [ ${CC} == 'gcc-6' ]; then
|
||||
build_opts='-D b_lundef=false'
|
||||
else
|
||||
build_opts=''
|
||||
fi
|
||||
meson -D werror=true ${build_opts} ../
|
||||
ninja
|
||||
|
||||
TEST_WITH_VALGRIND=true ${TEST_CMD}
|
||||
cd ..
|
||||
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
|
||||
cd ..
|
||||
done
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Meson 0.45 requires Python 3.5 or newer
|
||||
sudo python3 -m pip install pytest meson==0.44
|
||||
wget https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-linux.zip
|
||||
unzip ninja-linux.zip
|
||||
chmod 755 ninja
|
||||
sudo chown root:root ninja
|
||||
sudo mv -fv ninja /usr/local/bin
|
||||
valgrind --version
|
||||
ninja --version
|
||||
meson --version
|
||||
|
||||
# Install fuse
|
||||
wget https://github.com/libfuse/libfuse/archive/master.zip
|
||||
unzip master.zip
|
||||
cd libfuse-master
|
||||
mkdir build
|
||||
cd build
|
||||
export CC=gcc-6
|
||||
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/
|
||||
ls -d1 /usr/local/lib/*-linux-gnu | sudo tee /etc/ld.so.conf.d/usrlocal.conf
|
||||
sudo ldconfig
|
||||
|
||||
# Setup ssh
|
||||
ssh-keygen -b 768 -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"
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import pytest
|
||||
import os
|
||||
import stat
|
||||
import time
|
||||
from os.path import join as pjoin
|
||||
|
||||
basename = pjoin(os.path.dirname(__file__), '..')
|
||||
|
||||
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(mnt_dir):
|
||||
subprocess.call(['fusermount', '-z', '-u', mnt_dir],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.STDOUT)
|
||||
|
||||
def umount(mount_process, mnt_dir):
|
||||
subprocess.check_call(['fusermount', '-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('file system process terminated with code %s' % (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()`.
|
||||
'''
|
||||
|
||||
skip = lambda x: pytest.mark.skip(reason=x)
|
||||
|
||||
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('Unable to open /dev/fuse: %s' % 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