pax_global_header00006660000000000000000000000064127325554750014531gustar00rootroot0000000000000052 comment=b2fa7593586b141298e6159f40f521d2b0f4f894 sshfs-sshfs_2.8/000077500000000000000000000000001273255547500137365ustar00rootroot00000000000000sshfs-sshfs_2.8/.gitignore000066400000000000000000000010411273255547500157220ustar00rootroot00000000000000# # 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. .* !.gitignore *.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 sshfs-sshfs_2.8/AUTHORS000066400000000000000000000013711273255547500150100ustar00rootroot00000000000000Current Maintainer ------------------ Nikolaus Rath Past Maintainers ---------------- Miklos Szeredi (until 12/2015) Contributors (autogenerated list) --------------------------------- Alan Jenkins Alexander Neumann Benjamin Fleischer Chris Wolfe gala George Vlahavas Julio Merino Julio Merino Mike Kelly Miklos Szeredi Miklos Szeredi Nikolaus Rath Percy Jahn Qais Patankar Rian Hunter sshfs-sshfs_2.8/COPYING000066400000000000000000000432541273255547500150010ustar00rootroot00000000000000 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. Copyright (C) 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. , 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. sshfs-sshfs_2.8/ChangeLog000066400000000000000000000063541273255547500155200ustar00rootroot00000000000000Release 2.7 (2016-06-22) ------------------------ * Added support for the "fsync" extension. * Fixed a build problem with bitbake Release 2.6 (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 ) 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 sshfs-sshfs_2.8/FAQ000066400000000000000000000235111273255547500142720ustar00rootroot00000000000000SSHFS FAQ ========= 1. I've found a bug and there's no solution in this FAQ, what should I do? 2. Advantage of sshfs over NFS and Samba 3. Create the device node 4. mv fails with "Operation not permitted" 5. cvs fails with "cvs [status aborted]: cannot get working directory: No such file or directory" in a sshfs mounted directory 6. Changes on the server are not immediately visible in the mounted directory. 7. Configuring the ssh connection 8. What are the no_readahead and sshfs_sync options for? 9. Why does df return strange values on partitions mounted via sshfs? 10. How do I specify the remote mount point (since the example defaults to the home directory) 11. sshfs hangs after a while 12. Following symlinks on the server side 13. Making absolute symlinks work 14. Mounting as root 15. Exporting via NFS 16. Automatical mounting using /etc/fstab 17. Why does SVN (etc...) fail with permission denied? 18. Why does SVN (etc...) fail to rename files? 19. Is there some neat way to do it in reverse? 20. Might it be reasonable to disallow loops? 21. How to mount through an intermediary ssh server, eg: localhost -> A -> B; mount B from localhost 22. Alternative Solution 23. I seem to have successfully mounted a remote directory, but performing an `ls -l` on the directory above the mount point shows the mount point's attributes as `? ? ? ? ? ?`. Nothing shows up in the directory either. What am I doing wrong? -------------------------------------------------------------------------- 1. I've found a bug and there's no solution in this FAQ, what should I do? Please report the bug in the Github issue tracker: https://github.com/libfuse/sshfs/issues Also logs with debugging output can be useful for diagnosing the problem. Try running sshfs with the following options: sshfs -odebug,sshfs_debug,loglevel=debug ... Doing strace on the application which fails may also sometimes help: strace -f -o /tmp/strace application args ... Note that large messages (over 40k) will be rejected from the mailing list. So try to keep the logs as short as possible. 2. Advantage of sshfs over NFS and Samba Users can mount remote resources they already have ssh access to, without requiring the remote machine to export the resource. The remote resource can be mounted when it is needed in a location that is convenient for the user at that time, without needing to rely on a central, root-controlled file system table. Automatic mounting, if desired, can be added to a shell script such as .bashrc (provided authentication is done using RSA/DSA keys). Resources can be mounted over slow and unreliable (distant) connections. 3. Create the device node If you don't use udev, you may get this error message: fusermount: failed to open /dev/fuse: No such device or address Before loading the fuse kernel module, create the device node manually: mknod -m 666 /dev/fuse c 10 229 4. mv fails with "Operation not permitted". Use -o workaround=rename (requires sshfs version >= 1.3). 5. cvs fails with "cvs status aborted?: cannot get working directory: No such file or directory" in a sshfs mounted directory Use the -oreaddir_ino option. Example: sshfs -oreaddir_ino hostname:remote_dir mount_point 6. Changes on the server are not immediately visible in the mounted directory. By default, sshfs caches things for 20 seconds, use -o cache_timeout=N to change the default cache timeout (in seconds) or -o cache=no for disabling the cache. You can also control cache timeouts for directory listing etc with -o cache_stat_timeout=N, -o cache_dir_timout=N, and -o cache_link_timout=N. 7. Configuring the ssh connection In addition to flags like -C, -p, and -o SSHOPT...=, you may find it easier to edit your /.ssh/config file. You can add an entry with any customization you want, test it with ssh, and finally use it with sshfs. As a bonus, you get a short mnemonic for your configuration. 8. What are the no_readahead and sshfs_sync options for? These disable read and write optimizations respectively. They don't really make sense unless you're doing something special. 9. Why does df return strange values on partitions mounted via sshfs? Because the SFTP protocol doesn't have a statfs operation this is currently not possible to display proper usage on remote partition. 10. How do I specfy the remote mount point (since the example defaults to the home directory) The example shows: sshfs hostname: mountpoint To specify a remote mount point use: sshfs hostname:remotemountpoint mountpoint This might be obvious to others, but I ended up looking up the interface to sftp to see if I could learn how to specify the remote mount point, then thought about the way that scp specifies the remote directory, and it worked. 11. sshfs hangs after a while Mounting works fine, I can use the files in Mountpoint as good as any other files on my system, but after bit of time, changing nothing on the remote files sshfs crashes. This means, I can not cd into the Mountpoint (xterm hangs, nautilus hangs... every program trying to access the Mountpoint gets stuck, and won't return). Solution: add ServerAliveInterval 15 in your .ssh/config (or use -o ServerAliveInterval=15 on the sshfs command line but I did not test that solution). This will force the ssh connection to stay alive even if you have no activity. 12. Following symlinks on the server side The -o follow_symlinks option will enable this. 13. Making absolute symlinks work Use the -o transform_symlinks option, which will transform absolute symlinks (ones which point somewhere inside the mount) into relative ones. 14. Mounting as root Generally it's not possible to use an sshfs mount as a "real" filesystem shared between multiple users. Some of this functionality can be enabled with the -o allow_other and -o default_permissions options, but files will not be created with the correct ownership, etc... 15. Exporting via NFS Use the userspace NFS daemon http://sourceforge.net/projects/unfs 16. Automatical mounting using /etc/fstab A line in /etc/fstab has the following format: sshfs#USERNAME@REMOTE_HOST:REMOTE_PATH MOUNT_POINT fuse SSHFS_OPTIONS 0 0 eg. sshfs#guest@guest.login.com:data /mnt/guest fuse \ uid=1003,gid=100,umask=0,allow_other 0 0 17. Why does SVN (etc...) fail with permission denied? This is a bug that happens when an application creates a read-only file opened for writing (e.g. open("foo", O_WRONLY|O_CREAT, 0444)) It has been fixed in sshfs version 1.3, but also requires FUSE version >=2.5.X and Linux kernel version >=2.6.15. 18. Why does SVN (etc...) fail to rename files? $ svn co svn://anonsvn.kde.org/home/kde/trunk/KDE/kdelibs svn: Can't move 'kdelibs/.svn/tmp/entries' to 'kdelibs/.svn/entries': Operation not permitted The reason is that SFTP protocol version 3 (which is implemented by OpenSSH's sftp-server) defines the rename operation slightly differently than POSIX. The difference is that renaming to an existing file or directory will fail instead of atomically replacing the old file. The -o workaround=rename option will try to emulate POSIX rename semantics, but it cannot guarantee atomicity. In most of the cases this doesn't matter, and things will work fine with this option. 19. Is there some neat way to do it in reverse? You want to mount a USB thumb drive onto a file server that is rather remote. Assuming this is difficult because the laptop with the thumb drive is sitting behind NAT, firewalls, etc. then you need to create a port-forward: client$ ssh -R 2222:localhost:22 server server$ sshfs -p 2222 localhost:/media/usb1 myusb1 (Now, is there is there a smarter way that does not involve port opening login permissions in an undesireable direction?) 20. Might it be reasonable to disallow loops? sshfs localhost:/mnt /mnt This seems to produce undesirable results. --JoshuaRodman 21. How to mount through an intermediary ssh server, eg: localhost -> A -> B; mount B from localhost Start by mounting the folder you need that is on "a" to a folder on "b" then mount the new folder that is on "a" to a folder on localhost. IE: (These are NOT real commands, but a sequence of steps. A mounts B:/home/x on /mnt/Bx localhost mounts A:/mnt/Bx on ~/mydir 22. Alternative Solution: 1) Create a shell script to wrap the tunneling of one ssh command over another, $ cat >Atunnel <sshfs.1.tmp || exit 1; \ mv sshfs.1.tmp sshfs.1 if SSH_NODELAY_SO all-local: sshnodelay.so install-exec-local: sshnodelay.so test -z "$(libdir)" || $(mkdir_p) "$(DESTDIR)$(libdir)" $(INSTALL) -m 755 sshnodelay.so "$(DESTDIR)$(libdir)/sshnodelay.so" uninstall-local: rm -f "$(DESTDIR)$(libdir)/sshnodelay.so" sshnodelay.so: $(CC) -Wall -W -s --shared -fPIC $(sshnodelay_libs) sshnodelay.c -o sshnodelay.so endif sshfs-sshfs_2.8/README.md000066400000000000000000000040171273255547500152170ustar00rootroot00000000000000Abstract ======== 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 Latest version ============== The latest version and more information can be found on http://github.com/libfuse/sshfs 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 mountpoint Installing ========== First you need to download FUSE 2.2 or later from http://github.com/libfuse/libfuse. 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. If checking out from git for the first time also do `autoreconf -i` before doing `./configure`. sshfs-sshfs_2.8/cache.c000066400000000000000000000367541273255547500151640ustar00rootroot00000000000000/* Caching file system proxy Copyright (C) 2004 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ #include "cache.h" #include #include #include #include #include #include #define DEFAULT_CACHE_TIMEOUT_SECS 20 #define DEFAULT_MAX_CACHE_SIZE 10000 #define DEFAULT_CACHE_CLEAN_INTERVAL_SECS 60 #define DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS 5 struct cache { int on; unsigned int stat_timeout_secs; unsigned int dir_timeout_secs; unsigned int link_timeout_secs; unsigned int max_size; unsigned int clean_interval_secs; unsigned int min_clean_interval_secs; struct fuse_cache_operations *next_oper; GHashTable *table; pthread_mutex_t lock; time_t last_cleaned; uint64_t write_ctr; }; static struct cache cache; struct node { struct stat stat; time_t stat_valid; char **dir; time_t dir_valid; char *link; time_t link_valid; time_t valid; }; struct fuse_cache_dirhandle { const char *path; fuse_dirh_t h; fuse_dirfil_t filler; GPtrArray *dir; uint64_t wrctr; }; static void free_node(gpointer node_) { struct node *node = (struct node *) node_; g_strfreev(node->dir); g_free(node); } static int cache_clean_entry(void *key_, struct node *node, time_t *now) { (void) key_; if (*now > node->valid) return TRUE; else return FALSE; } static void cache_clean(void) { time_t now = time(NULL); if (now > cache.last_cleaned + cache.min_clean_interval_secs && (g_hash_table_size(cache.table) > cache.max_size || now > cache.last_cleaned + cache.clean_interval_secs)) { g_hash_table_foreach_remove(cache.table, (GHRFunc) cache_clean_entry, &now); cache.last_cleaned = now; } } static struct node *cache_lookup(const char *path) { return (struct node *) g_hash_table_lookup(cache.table, path); } static void cache_purge(const char *path) { g_hash_table_remove(cache.table, path); } static void cache_purge_parent(const char *path) { const char *s = strrchr(path, '/'); if (s) { if (s == path) g_hash_table_remove(cache.table, "/"); else { char *parent = g_strndup(path, s - path); cache_purge(parent); g_free(parent); } } } void cache_invalidate(const char *path) { if (!cache.on) return; pthread_mutex_lock(&cache.lock); cache_purge(path); pthread_mutex_unlock(&cache.lock); } void cache_invalidate_write(const char *path) { pthread_mutex_lock(&cache.lock); cache_purge(path); cache.write_ctr++; pthread_mutex_unlock(&cache.lock); } static void cache_invalidate_dir(const char *path) { pthread_mutex_lock(&cache.lock); cache_purge(path); cache_purge_parent(path); pthread_mutex_unlock(&cache.lock); } static int cache_del_children(const char *key, void *val_, const char *path) { (void) val_; if (strncmp(key, path, strlen(path)) == 0) return TRUE; else return FALSE; } static void cache_do_rename(const char *from, const char *to) { pthread_mutex_lock(&cache.lock); g_hash_table_foreach_remove(cache.table, (GHRFunc) cache_del_children, (char *) from); cache_purge(from); cache_purge(to); cache_purge_parent(from); cache_purge_parent(to); pthread_mutex_unlock(&cache.lock); } static struct node *cache_get(const char *path) { struct node *node = cache_lookup(path); if (node == NULL) { char *pathcopy = g_strdup(path); node = g_new0(struct node, 1); g_hash_table_insert(cache.table, pathcopy, node); } return node; } void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr) { struct node *node; if (!cache.on) return; pthread_mutex_lock(&cache.lock); if (wrctr == cache.write_ctr) { node = cache_get(path); node->stat = *stbuf; node->stat_valid = time(NULL) + cache.stat_timeout_secs; if (node->stat_valid > node->valid) node->valid = node->stat_valid; cache_clean(); } pthread_mutex_unlock(&cache.lock); } static void cache_add_dir(const char *path, char **dir) { struct node *node; pthread_mutex_lock(&cache.lock); node = cache_get(path); g_strfreev(node->dir); node->dir = dir; node->dir_valid = time(NULL) + cache.dir_timeout_secs; if (node->dir_valid > node->valid) node->valid = node->dir_valid; cache_clean(); pthread_mutex_unlock(&cache.lock); } static size_t my_strnlen(const char *s, size_t maxsize) { const char *p; for (p = s; maxsize && *p; maxsize--, p++); return p - s; } static void cache_add_link(const char *path, const char *link, size_t size) { struct node *node; pthread_mutex_lock(&cache.lock); node = cache_get(path); g_free(node->link); node->link = g_strndup(link, my_strnlen(link, size-1)); node->link_valid = time(NULL) + cache.link_timeout_secs; if (node->link_valid > node->valid) node->valid = node->link_valid; cache_clean(); pthread_mutex_unlock(&cache.lock); } static int cache_get_attr(const char *path, struct stat *stbuf) { struct node *node; int err = -EAGAIN; pthread_mutex_lock(&cache.lock); node = cache_lookup(path); if (node != NULL) { time_t now = time(NULL); if (node->stat_valid - now >= 0) { *stbuf = node->stat; err = 0; } } pthread_mutex_unlock(&cache.lock); return err; } uint64_t cache_get_write_ctr(void) { uint64_t res; pthread_mutex_lock(&cache.lock); res = cache.write_ctr; pthread_mutex_unlock(&cache.lock); return res; } static int cache_getattr(const char *path, struct stat *stbuf) { int err = cache_get_attr(path, stbuf); if (err) { uint64_t wrctr = cache_get_write_ctr(); err = cache.next_oper->oper.getattr(path, stbuf); if (!err) cache_add_attr(path, stbuf, wrctr); } return err; } static int cache_readlink(const char *path, char *buf, size_t size) { struct node *node; int err; pthread_mutex_lock(&cache.lock); node = cache_lookup(path); if (node != NULL) { time_t now = time(NULL); if (node->link_valid - now >= 0) { strncpy(buf, node->link, size-1); buf[size-1] = '\0'; pthread_mutex_unlock(&cache.lock); return 0; } } pthread_mutex_unlock(&cache.lock); err = cache.next_oper->oper.readlink(path, buf, size); if (!err) cache_add_link(path, buf, size); return err; } static int cache_dirfill(fuse_cache_dirh_t ch, const char *name, const struct stat *stbuf) { int err = ch->filler(ch->h, name, 0, 0); if (!err) { g_ptr_array_add(ch->dir, g_strdup(name)); if (stbuf->st_mode & S_IFMT) { char *fullpath; const char *basepath = !ch->path[1] ? "" : ch->path; fullpath = g_strdup_printf("%s/%s", basepath, name); cache_add_attr(fullpath, stbuf, ch->wrctr); g_free(fullpath); } } return err; } static int cache_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler) { struct fuse_cache_dirhandle ch; int err; char **dir; struct node *node; pthread_mutex_lock(&cache.lock); node = cache_lookup(path); if (node != NULL && node->dir != NULL) { time_t now = time(NULL); if (node->dir_valid - now >= 0) { for(dir = node->dir; *dir != NULL; dir++) filler(h, *dir, 0, 0); pthread_mutex_unlock(&cache.lock); return 0; } } pthread_mutex_unlock(&cache.lock); ch.path = path; ch.h = h; ch.filler = filler; ch.dir = g_ptr_array_new(); ch.wrctr = cache_get_write_ctr(); err = cache.next_oper->cache_getdir(path, &ch, cache_dirfill); g_ptr_array_add(ch.dir, NULL); dir = (char **) ch.dir->pdata; if (!err) cache_add_dir(path, dir); else g_strfreev(dir); g_ptr_array_free(ch.dir, FALSE); return err; } static int cache_unity_dirfill(fuse_cache_dirh_t ch, const char *name, const struct stat *stbuf) { (void) stbuf; return ch->filler(ch->h, name, 0, 0); } static int cache_unity_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler) { struct fuse_cache_dirhandle ch; ch.h = h; ch.filler = filler; return cache.next_oper->cache_getdir(path, &ch, cache_unity_dirfill); } static int cache_mknod(const char *path, mode_t mode, dev_t rdev) { int err = cache.next_oper->oper.mknod(path, mode, rdev); if (!err) cache_invalidate_dir(path); return err; } static int cache_mkdir(const char *path, mode_t mode) { int err = cache.next_oper->oper.mkdir(path, mode); if (!err) cache_invalidate_dir(path); return err; } static int cache_unlink(const char *path) { int err = cache.next_oper->oper.unlink(path); if (!err) cache_invalidate_dir(path); return err; } static int cache_rmdir(const char *path) { int err = cache.next_oper->oper.rmdir(path); if (!err) cache_invalidate_dir(path); return err; } static int cache_symlink(const char *from, const char *to) { int err = cache.next_oper->oper.symlink(from, to); if (!err) cache_invalidate_dir(to); return err; } static int cache_rename(const char *from, const char *to) { int err = cache.next_oper->oper.rename(from, to); if (!err) cache_do_rename(from, to); return err; } static int cache_link(const char *from, const char *to) { int err = cache.next_oper->oper.link(from, to); if (!err) { cache_invalidate(from); cache_invalidate_dir(to); } return err; } static int cache_chmod(const char *path, mode_t mode) { int err = cache.next_oper->oper.chmod(path, mode); if (!err) cache_invalidate(path); return err; } static int cache_chown(const char *path, uid_t uid, gid_t gid) { int err = cache.next_oper->oper.chown(path, uid, gid); if (!err) cache_invalidate(path); return err; } static int cache_truncate(const char *path, off_t size) { int err = cache.next_oper->oper.truncate(path, size); if (!err) cache_invalidate(path); return err; } static int cache_utime(const char *path, struct utimbuf *buf) { int err = cache.next_oper->oper.utime(path, buf); if (!err) cache_invalidate(path); return err; } static int cache_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { int res = cache.next_oper->oper.write(path, buf, size, offset, fi); if (res >= 0) cache_invalidate_write(path); return res; } #if FUSE_VERSION >= 25 static int cache_create(const char *path, mode_t mode, struct fuse_file_info *fi) { int err = cache.next_oper->oper.create(path, mode, fi); if (!err) cache_invalidate_dir(path); return err; } static int cache_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) { int err = cache.next_oper->oper.ftruncate(path, size, fi); if (!err) cache_invalidate(path); return err; } static int cache_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) { int err = cache_get_attr(path, stbuf); if (err) { uint64_t wrctr = cache_get_write_ctr(); err = cache.next_oper->oper.fgetattr(path, stbuf, fi); if (!err) cache_add_attr(path, stbuf, wrctr); } return err; } #endif static void cache_unity_fill(struct fuse_cache_operations *oper, struct fuse_operations *cache_oper) { #if FUSE_VERSION >= 23 cache_oper->init = oper->oper.init; #endif cache_oper->getattr = oper->oper.getattr; cache_oper->access = oper->oper.access; cache_oper->readlink = oper->oper.readlink; cache_oper->getdir = cache_unity_getdir; cache_oper->mknod = oper->oper.mknod; cache_oper->mkdir = oper->oper.mkdir; cache_oper->symlink = oper->oper.symlink; cache_oper->unlink = oper->oper.unlink; cache_oper->rmdir = oper->oper.rmdir; cache_oper->rename = oper->oper.rename; cache_oper->link = oper->oper.link; cache_oper->chmod = oper->oper.chmod; cache_oper->chown = oper->oper.chown; cache_oper->truncate = oper->oper.truncate; cache_oper->utime = oper->oper.utime; cache_oper->open = oper->oper.open; cache_oper->read = oper->oper.read; cache_oper->write = oper->oper.write; cache_oper->flush = oper->oper.flush; cache_oper->release = oper->oper.release; cache_oper->fsync = oper->oper.fsync; cache_oper->statfs = oper->oper.statfs; cache_oper->setxattr = oper->oper.setxattr; cache_oper->getxattr = oper->oper.getxattr; cache_oper->listxattr = oper->oper.listxattr; cache_oper->removexattr = oper->oper.removexattr; #if FUSE_VERSION >= 25 cache_oper->create = oper->oper.create; cache_oper->ftruncate = oper->oper.ftruncate; cache_oper->fgetattr = oper->oper.fgetattr; #endif #if FUSE_VERSION >= 29 cache_oper->flag_nullpath_ok = oper->oper.flag_nullpath_ok; cache_oper->flag_nopath = oper->oper.flag_nopath; #endif } static void cache_fill(struct fuse_cache_operations *oper, struct fuse_operations *cache_oper) { cache_oper->getattr = oper->oper.getattr ? cache_getattr : NULL; cache_oper->readlink = oper->oper.readlink ? cache_readlink : NULL; cache_oper->getdir = oper->cache_getdir ? cache_getdir : NULL; cache_oper->mknod = oper->oper.mknod ? cache_mknod : NULL; cache_oper->mkdir = oper->oper.mkdir ? cache_mkdir : NULL; cache_oper->symlink = oper->oper.symlink ? cache_symlink : NULL; cache_oper->unlink = oper->oper.unlink ? cache_unlink : NULL; cache_oper->rmdir = oper->oper.rmdir ? cache_rmdir : NULL; cache_oper->rename = oper->oper.rename ? cache_rename : NULL; cache_oper->link = oper->oper.link ? cache_link : NULL; cache_oper->chmod = oper->oper.chmod ? cache_chmod : NULL; cache_oper->chown = oper->oper.chown ? cache_chown : NULL; cache_oper->truncate = oper->oper.truncate ? cache_truncate : NULL; cache_oper->utime = oper->oper.utime ? cache_utime : NULL; cache_oper->write = oper->oper.write ? cache_write : NULL; #if FUSE_VERSION >= 25 cache_oper->create = oper->oper.create ? cache_create : NULL; cache_oper->ftruncate = oper->oper.ftruncate ? cache_ftruncate : NULL; cache_oper->fgetattr = oper->oper.fgetattr ? cache_fgetattr : NULL; #endif #if FUSE_VERSION >= 29 cache_oper->flag_nullpath_ok = 0; cache_oper->flag_nopath = 0; #endif } struct fuse_operations *cache_init(struct fuse_cache_operations *oper) { static struct fuse_operations cache_oper; cache.next_oper = oper; cache_unity_fill(oper, &cache_oper); if (cache.on) { cache_fill(oper, &cache_oper); pthread_mutex_init(&cache.lock, NULL); cache.table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_node); if (cache.table == NULL) { fprintf(stderr, "failed to create cache\n"); return NULL; } } return &cache_oper; } static const struct fuse_opt cache_opts[] = { { "cache=yes", offsetof(struct cache, on), 1 }, { "cache=no", offsetof(struct cache, on), 0 }, { "cache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 }, { "cache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 }, { "cache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 }, { "cache_stat_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 }, { "cache_dir_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 }, { "cache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 }, { "cache_max_size=%u", offsetof(struct cache, max_size), 0 }, { "cache_clean_interval=%u", offsetof(struct cache, clean_interval_secs), 0 }, { "cache_min_clean_interval=%u", offsetof(struct cache, min_clean_interval_secs), 0 }, FUSE_OPT_END }; int cache_parse_options(struct fuse_args *args) { cache.stat_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS; cache.dir_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS; cache.link_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS; cache.max_size = DEFAULT_MAX_CACHE_SIZE; cache.clean_interval_secs = DEFAULT_CACHE_CLEAN_INTERVAL_SECS; cache.min_clean_interval_secs = DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS; cache.on = 1; return fuse_opt_parse(args, &cache, cache_opts, NULL); } sshfs-sshfs_2.8/cache.h000066400000000000000000000016671273255547500151640ustar00rootroot00000000000000/* Caching file system proxy Copyright (C) 2004 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ #include #include #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(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); sshfs-sshfs_2.8/compat/000077500000000000000000000000001273255547500152215ustar00rootroot00000000000000sshfs-sshfs_2.8/compat/darwin_compat.c000066400000000000000000000136751273255547500202300ustar00rootroot00000000000000/* * Copyright (c) 2006-2008 Amit Singh/Google Inc. * Copyright (c) 2012 Anatol Pomozov * Copyright (c) 2011-2013 Benjamin Fleischer */ #include "darwin_compat.h" #include #include #include /* * Semaphore implementation based on: * * Copyright (C) 2000,02 Free Software Foundation, Inc. * This file is part of the GNU C Library. * Written by Gal Le Mignot * * 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; } sshfs-sshfs_2.8/compat/darwin_compat.h000066400000000000000000000025401273255547500202220ustar00rootroot00000000000000/* * Copyright (c) 2006-2008 Amit Singh/Google Inc. * Copyright (c) 2011-2013 Benjamin Fleischer */ #ifndef _DARWIN_COMPAT_ #define _DARWIN_COMPAT_ #include /* 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 */ 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_ */ sshfs-sshfs_2.8/compat/fuse_opt.c000066400000000000000000000217321273255547500172160ustar00rootroot00000000000000/* FUSE: Filesystem in Userspace Copyright (C) 2001-2006 Miklos Szeredi This program can be distributed under the terms of the GNU LGPL. See the file COPYING.LIB */ #include "fuse_opt.h" #include #include #include #include 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; } sshfs-sshfs_2.8/compat/fuse_opt.h000066400000000000000000000161661273255547500172300ustar00rootroot00000000000000/* FUSE: Filesystem in Userspace Copyright (C) 2001-2006 Miklos Szeredi 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 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_ */ sshfs-sshfs_2.8/configure.ac000066400000000000000000000040641273255547500162300ustar00rootroot00000000000000AC_INIT(sshfs, 2.8) AC_CANONICAL_TARGET AM_INIT_AUTOMAKE([foreign subdir-objects]) AM_CONFIG_HEADER(config.h) AC_PROG_CC AM_PROG_CC_C_O CFLAGS="$CFLAGS -Wall -W" LIBS= AC_SEARCH_LIBS(dlsym, [dl]) sshnodelay_libs=$LIBS AC_SUBST(sshnodelay_libs) LIBS= case "$target_os" in *linux*) osname=linux;; *darwin*) osname=darwin;; *) osname=unknown;; esac AC_ARG_ENABLE(sshnodelay, [ --disable-sshnodelay Don't compile NODELAY workaround for ssh]) if test -z "$enable_sshnodelay"; then AC_MSG_CHECKING([OpenSSH version]) [eval `ssh -V 2>&1 | sed -n 's/^OpenSSH_\([1-9][0-9]*\)\.\([0-9][0-9]*\).*/ssh_major=\1 ssh_minor=\2/p'`] if test "x$ssh_major" != x -a "x$ssh_minor" != x; then if test $ssh_major -gt 4 -o \( $ssh_major = 4 -a $ssh_minor -ge 4 \); then AC_MSG_RESULT([$ssh_major.$ssh_minor >= 4.4, disabling NODELAY workaround]) enable_sshnodelay=no else AC_MSG_RESULT([$ssh_major.$ssh_minor < 4.4, enabling NODELAY workaround]) enable_sshnodelay=yes fi else AC_MSG_RESULT([not found]) fi fi if test "$enable_sshnodelay" = "yes"; then AC_DEFINE(SSH_NODELAY_WORKAROUND, 1, [Compile ssh NODELAY workaround]) fi AM_CONDITIONAL(SSH_NODELAY_SO, test "$enable_sshnodelay" = "yes") export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH PKG_CHECK_MODULES([SSHFS], [fuse >= 2.3 glib-2.0 gthread-2.0]) have_fuse_opt_parse=no oldlibs="$LIBS" LIBS="$LIBS $SSHFS_LIBS" AC_CHECK_FUNC([fuse_opt_parse], [have_fuse_opt_parse=yes]) LIBS="$oldlibs" if test "$have_fuse_opt_parse" = no -o "$osname" = darwin; then CFLAGS="$CFLAGS -I${srcdir}/compat" fi AM_CONDITIONAL(FUSE_OPT_COMPAT, test "$have_fuse_opt_parse" = no) AM_CONDITIONAL(DARWIN_COMPAT, test "$osname" = darwin) AC_CHECK_PROG(UNMOUNT_COMMAND, fusermount, fusermount -u, umount) # TODO: Figure out why we special-case this in Darwin. Would be nice if # the default setting was consistent across platforms so we wouldn't need # to care about it here. case "$osname" in darwin) IDMAP_DEFAULT=user ;; *) IDMAP_DEFAULT=none ;; esac AC_SUBST(IDMAP_DEFAULT) AC_CONFIG_FILES([Makefile]) AC_OUTPUT sshfs-sshfs_2.8/sshfs.1.in000066400000000000000000000141641273255547500155610ustar00rootroot00000000000000.TH SSHFS "1" "April 2008" "SSHFS version 2.0" "User Commands" .SH NAME SSHFS \- filesystem client based on ssh .SH SYNOPSIS .SS mounting .TP \fBsshfs\fP [\fIuser\fP@]\fBhost\fP:[\fIdir\fP] \fBmountpoint\fP [\fIoptions\fP] .SS unmounting .TP \fB__UNMOUNT_COMMAND__ mountpoint\fP .SH DESCRIPTION SSHFS (Secure SHell FileSystem) is a file system for Linux (and other operating systems with a FUSE implementation, such as Mac OS X or FreeBSD) capable of operating on files on a remote computer using just a secure shell login on the remote computer. On the local computer where the SSHFS is mounted, the implementation makes use of the FUSE (Filesystem in Userspace) kernel module. The practical effect of this is that the end user can seamlessly interact with remote files being securely served over SSH just as if they were local files on his/her computer. On the remote computer the SFTP subsystem of SSH is used. .PP If \fIhost\fP is a numeric IPv6 address, it needs to be enclosed in square brackets. .SH OPTIONS .SS "general options:" .TP \fB\-o\fR opt,[opt...] mount options .TP \fB\-h\fR \fB\-\-help\fR print help .TP \fB\-V\fR \fB\-\-version\fR print version .SS "SSHFS options:" .TP \fB\-p\fR PORT equivalent to '\-o port=PORT' .TP \fB\-C\fR equivalent to '\-o compression=yes' .TP \fB\-F\fR ssh_configfile specifies alternative ssh configuration file .TP \fB\-1\fR equivalent to '\-o ssh_protocol=1' .TP \fB\-o\fR reconnect reconnect to server .TP \fB\-o\fR delay_connect delay connection to server .TP \fB\-o\fR sshfs_sync synchronous writes .TP \fB\-o\fR no_readahead synchronous reads (no speculative readahead) .TP \fB\-o\fR sync_readdir synchronous readdir .TP \fB\-o\fR sshfs_debug print some debugging information .TP \fB\-o\fR cache=BOOL enable caching {yes,no} (default: yes) .TP \fB\-o\fR cache_timeout=N sets timeout for caches in seconds (default: 20) .TP \fB\-o\fR cache_X_timeout=N sets timeout for {stat,dir,link} cache .TP \fB\-o\fR workaround=LIST colon separated list of workarounds .RS 8 .TP none no workarounds enabled .TP all all workarounds enabled .TP [no]rename fix renaming to existing file (default: off) .TP [no]nodelaysrv set nodelay tcp flag in ssh (default: off) .TP [no]truncate fix truncate for old servers (default: off) .TP [no]buflimit fix buffer fillup bug in server (default: on) .RE .TP \fB\-o\fR idmap=TYPE user/group ID mapping (default: __IDMAP_DEFAULT__) .RS 8 .TP none no translation of the ID space .TP user only translate UID/GID of connecting user .TP file translate UIDs/GIDs based upon the contents of \fBuidfile \fR and \fBgidfile\fR .RE .TP \fB\-o\fR uidfile=FILE file containing username:uid mappings for \fBidmap=file\fR .RE .TP \fB\-o\fR gidfile=FILE file containing groupname:gid mappings for \fBidmap=file\fR .RE .TP \fB\-o\fR nomap=TYPE with idmap=file, how to handle missing mappings .RS 8 .TP ignore don't do any re-mapping .TP error return an error (default) .RE .TP \fB\-o\fR ssh_command=CMD execute CMD instead of 'ssh' .TP \fB\-o\fR ssh_protocol=N ssh protocol to use (default: 2) .TP \fB\-o\fR sftp_server=SERV path to sftp server or subsystem (default: sftp) .TP \fB\-o\fR directport=PORT directly connect to PORT bypassing ssh \fB\-o\fR slave communicate over stdin and stdout bypassing network .TP \fB\-o\fR slave communicate over stdin and stdout bypassing network .TP \fB\-o\fR disable_hardlink link(2) will return with errno set to ENOSYS. Hard links don't currently work perfectly on sshfs, and this confuses some programs. If that happens try disabling hard links with this option. .TP \fB\-o\fR transform_symlinks transform absolute symlinks to relative .TP \fB\-o\fR follow_symlinks follow symlinks on the server .TP \fB\-o\fR no_check_root don't check for existence of 'dir' on server .TP \fB\-o\fR password_stdin read password from stdin (only for pam_mount!) .TP \fB\-o\fR SSHOPT=VAL ssh options (see man ssh_config) .SS "FUSE options:" .TP \fB\-d\fR \fB\-o\fR debug enable debug output (implies \fB\-f\fR) .TP \fB\-f\fR foreground operation .TP \fB\-s\fR disable multi\-threaded operation .TP \fB\-o\fR allow_other allow access to other users .TP \fB\-o\fR allow_root allow access to root .TP \fB\-o\fR nonempty allow mounts over non\-empty file/dir .HP \fB\-o\fR default_permissions enable permission checking by kernel .TP \fB\-o\fR fsname=NAME set filesystem name .TP \fB\-o\fR subtype=NAME set filesystem type .TP \fB\-o\fR large_read issue large read requests (2.4 only) .TP \fB\-o\fR max_read=N set maximum size of read requests .TP \fB\-o\fR hard_remove immediate removal (don't hide files) .TP \fB\-o\fR use_ino let filesystem set inode numbers .TP \fB\-o\fR readdir_ino try to fill in d_ino in readdir .TP \fB\-o\fR direct_io use direct I/O .TP \fB\-o\fR kernel_cache cache files in kernel .TP \fB\-o\fR [no]auto_cache enable caching based on modification times .TP \fB\-o\fR umask=M set file permissions (octal) .TP \fB\-o\fR uid=N set file owner .TP \fB\-o\fR gid=N set file group .TP \fB\-o\fR entry_timeout=T cache timeout for names (1.0s) .TP \fB\-o\fR negative_timeout=T cache timeout for deleted names (0.0s) .TP \fB\-o\fR attr_timeout=T cache timeout for attributes (1.0s) .TP \fB\-o\fR ac_attr_timeout=T auto cache timeout for attributes (attr_timeout) .TP \fB\-o\fR intr allow requests to be interrupted .TP \fB\-o\fR intr_signal=NUM signal to send on interrupt (10) .TP \fB\-o\fR modules=M1[:M2...] names of modules to push onto filesystem stack .TP \fB\-o\fR max_write=N set maximum size of write requests .TP \fB\-o\fR max_readahead=N set maximum readahead .TP \fB\-o\fR async_read perform reads asynchronously (default) .TP \fB\-o\fR sync_read perform reads synchronously .SS "Module options:" .TP [subdir] .TP \fB\-o\fR subdir=DIR prepend this directory to all paths (mandatory) .TP \fB\-o\fR [no]rellinks transform absolute symlinks to relative .TP [iconv] .TP \fB\-o\fR from_code=CHARSET original encoding of file names (default: UTF-8) .TP \fB\-o\fR to_code=CHARSET new encoding of the file names (default: ISO-8859-2) .PD .SH "AUTHORS" .LP SSHFS has been written by Miklos Szeredi . .LP This man page was written by Bartosz Fenski for the Debian GNU/Linux distribution (but it may be used by others). sshfs-sshfs_2.8/sshfs.c000066400000000000000000002733101273255547500152360ustar00rootroot00000000000000/* SSH file system Copyright (C) 2004 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ #define _GNU_SOURCE /* avoid implicit declaration of *pt* functions */ #include "config.h" #include #include #include #ifdef __APPLE__ # include #endif #include #include #include #include #include #include #include #include #ifndef __APPLE__ # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ # include # include # include #endif #include "cache.h" #ifndef MAP_LOCKED #define MAP_LOCKED 0 #endif #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) #define MAP_ANONYMOUS MAP_ANON #endif #define SSH_FXP_INIT 1 #define SSH_FXP_VERSION 2 #define SSH_FXP_OPEN 3 #define SSH_FXP_CLOSE 4 #define SSH_FXP_READ 5 #define SSH_FXP_WRITE 6 #define SSH_FXP_LSTAT 7 #define SSH_FXP_FSTAT 8 #define SSH_FXP_SETSTAT 9 #define SSH_FXP_FSETSTAT 10 #define SSH_FXP_OPENDIR 11 #define SSH_FXP_READDIR 12 #define SSH_FXP_REMOVE 13 #define SSH_FXP_MKDIR 14 #define SSH_FXP_RMDIR 15 #define SSH_FXP_REALPATH 16 #define SSH_FXP_STAT 17 #define SSH_FXP_RENAME 18 #define SSH_FXP_READLINK 19 #define SSH_FXP_SYMLINK 20 #define SSH_FXP_STATUS 101 #define SSH_FXP_HANDLE 102 #define SSH_FXP_DATA 103 #define SSH_FXP_NAME 104 #define SSH_FXP_ATTRS 105 #define SSH_FXP_EXTENDED 200 #define SSH_FXP_EXTENDED_REPLY 201 #define SSH_FILEXFER_ATTR_SIZE 0x00000001 #define SSH_FILEXFER_ATTR_UIDGID 0x00000002 #define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 #define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 #define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 #define SSH_FX_OK 0 #define SSH_FX_EOF 1 #define SSH_FX_NO_SUCH_FILE 2 #define SSH_FX_PERMISSION_DENIED 3 #define SSH_FX_FAILURE 4 #define SSH_FX_BAD_MESSAGE 5 #define SSH_FX_NO_CONNECTION 6 #define SSH_FX_CONNECTION_LOST 7 #define SSH_FX_OP_UNSUPPORTED 8 #define SSH_FXF_READ 0x00000001 #define SSH_FXF_WRITE 0x00000002 #define SSH_FXF_APPEND 0x00000004 #define SSH_FXF_CREAT 0x00000008 #define SSH_FXF_TRUNC 0x00000010 #define SSH_FXF_EXCL 0x00000020 /* statvfs@openssh.com f_flag flags */ #define SSH2_FXE_STATVFS_ST_RDONLY 0x00000001 #define SSH2_FXE_STATVFS_ST_NOSUID 0x00000002 #define SFTP_EXT_POSIX_RENAME "posix-rename@openssh.com" #define SFTP_EXT_STATVFS "statvfs@openssh.com" #define SFTP_EXT_HARDLINK "hardlink@openssh.com" #define SFTP_EXT_FSYNC "fsync@openssh.com" #define PROTO_VERSION 3 #define MY_EOF 1 #define MAX_REPLY_LEN (1 << 17) #define RENAME_TEMP_CHARS 8 #define SFTP_SERVER_PATH "/usr/lib/sftp-server" #define SSHNODELAY_SO "sshnodelay.so" /* Asynchronous readdir parameters */ #define READDIR_START 2 #define READDIR_MAX 32 #define MAX_PASSWORD 1024 #ifdef __APPLE__ static char sshfs_program_path[PATH_MAX] = { 0 }; #endif /* __APPLE__ */ struct buffer { uint8_t *p; size_t len; size_t size; }; struct list_head { struct list_head *prev; struct list_head *next; }; struct request; typedef void (*request_func)(struct request *); struct request { unsigned int want_reply; sem_t ready; uint8_t reply_type; uint32_t id; int replied; int error; struct buffer reply; struct timeval start; void *data; request_func end_func; size_t len; struct list_head list; }; struct sshfs_io { int num_reqs; pthread_cond_t finished; int error; }; struct read_req { struct sshfs_io *sio; struct list_head list; struct buffer data; size_t size; ssize_t res; }; struct read_chunk { off_t offset; size_t size; int refs; long modifver; struct list_head reqs; struct sshfs_io sio; }; struct sshfs_file { struct buffer handle; struct list_head write_reqs; pthread_cond_t write_finished; int write_error; struct read_chunk *readahead; off_t next_pos; int is_seq; int connver; int modifver; int refs; }; struct sshfs { char *directport; char *ssh_command; char *sftp_server; struct fuse_args ssh_args; char *workarounds; int rename_workaround; int nodelay_workaround; int nodelaysrv_workaround; int truncate_workaround; int buflimit_workaround; int fstat_workaround; int transform_symlinks; int follow_symlinks; int no_check_root; int detect_uid; int idmap; int nomap; int disable_hardlink; char *uid_file; char *gid_file; GHashTable *uid_map; GHashTable *gid_map; GHashTable *r_uid_map; GHashTable *r_gid_map; unsigned max_read; unsigned max_write; unsigned ssh_ver; int sync_write; int sync_read; int sync_readdir; int debug; int foreground; int reconnect; int delay_connect; int slave; char *host; char *base_path; GHashTable *reqtab; pthread_mutex_t lock; pthread_mutex_t lock_write; int processing_thread_started; unsigned int randseed; int rfd; int wfd; int ptyfd; int ptyslavefd; int connver; int server_version; unsigned remote_uid; unsigned local_uid; #ifdef __APPLE__ unsigned remote_gid; unsigned local_gid; #endif int remote_uid_detected; unsigned blksize; char *progname; long modifver; unsigned outstanding_len; unsigned max_outstanding_len; pthread_cond_t outstanding_cond; int password_stdin; char *password; int ext_posix_rename; int ext_statvfs; int ext_hardlink; int ext_fsync; mode_t mnt_mode; struct fuse_operations *op; /* statistics */ uint64_t bytes_sent; uint64_t bytes_received; uint64_t num_sent; uint64_t num_received; unsigned int min_rtt; unsigned int max_rtt; uint64_t total_rtt; unsigned int num_connect; }; static struct sshfs sshfs; static const char *ssh_opts[] = { "AddressFamily", "BatchMode", "BindAddress", "ChallengeResponseAuthentication", "CheckHostIP", "Cipher", "Ciphers", "Compression", "CompressionLevel", "ConnectionAttempts", "ConnectTimeout", "ControlMaster", "ControlPath", "GlobalKnownHostsFile", "GSSAPIAuthentication", "GSSAPIDelegateCredentials", "HostbasedAuthentication", "HostKeyAlgorithms", "HostKeyAlias", "HostName", "IdentitiesOnly", "IdentityFile", "KbdInteractiveAuthentication", "KbdInteractiveDevices", "LocalCommand", "LogLevel", "MACs", "NoHostAuthenticationForLocalhost", "NumberOfPasswordPrompts", "PasswordAuthentication", "Port", "PreferredAuthentications", "ProxyCommand", "PubkeyAuthentication", "RekeyLimit", "RhostsRSAAuthentication", "RSAAuthentication", "ServerAliveCountMax", "ServerAliveInterval", "SmartcardDevice", "StrictHostKeyChecking", "TCPKeepAlive", "UsePrivilegedPort", "UserKnownHostsFile", "VerifyHostKeyDNS", NULL, }; enum { KEY_PORT, KEY_COMPRESS, KEY_HELP, KEY_VERSION, KEY_FOREGROUND, KEY_CONFIGFILE, }; enum { IDMAP_NONE, IDMAP_USER, IDMAP_FILE, }; enum { NOMAP_IGNORE, NOMAP_ERROR, }; #define SSHFS_OPT(t, p, v) { t, offsetof(struct sshfs, p), v } static struct fuse_opt sshfs_opts[] = { SSHFS_OPT("directport=%s", directport, 0), SSHFS_OPT("ssh_command=%s", ssh_command, 0), SSHFS_OPT("sftp_server=%s", sftp_server, 0), SSHFS_OPT("max_read=%u", max_read, 0), SSHFS_OPT("max_write=%u", max_write, 0), SSHFS_OPT("ssh_protocol=%u", ssh_ver, 0), SSHFS_OPT("-1", ssh_ver, 1), SSHFS_OPT("workaround=%s", workarounds, 0), SSHFS_OPT("idmap=none", idmap, IDMAP_NONE), SSHFS_OPT("idmap=user", idmap, IDMAP_USER), SSHFS_OPT("idmap=file", idmap, IDMAP_FILE), SSHFS_OPT("uidfile=%s", uid_file, 0), SSHFS_OPT("gidfile=%s", gid_file, 0), SSHFS_OPT("nomap=ignore", nomap, NOMAP_IGNORE), SSHFS_OPT("nomap=error", nomap, NOMAP_ERROR), SSHFS_OPT("sshfs_sync", sync_write, 1), SSHFS_OPT("no_readahead", sync_read, 1), SSHFS_OPT("sync_readdir", sync_readdir, 1), SSHFS_OPT("sshfs_debug", debug, 1), SSHFS_OPT("reconnect", reconnect, 1), SSHFS_OPT("transform_symlinks", transform_symlinks, 1), SSHFS_OPT("follow_symlinks", follow_symlinks, 1), SSHFS_OPT("no_check_root", no_check_root, 1), SSHFS_OPT("password_stdin", password_stdin, 1), SSHFS_OPT("delay_connect", delay_connect, 1), SSHFS_OPT("slave", slave, 1), SSHFS_OPT("disable_hardlink", disable_hardlink, 1), FUSE_OPT_KEY("-p ", KEY_PORT), FUSE_OPT_KEY("-C", KEY_COMPRESS), FUSE_OPT_KEY("-V", KEY_VERSION), FUSE_OPT_KEY("--version", KEY_VERSION), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("debug", KEY_FOREGROUND), FUSE_OPT_KEY("-d", KEY_FOREGROUND), FUSE_OPT_KEY("-f", KEY_FOREGROUND), FUSE_OPT_KEY("-F ", KEY_CONFIGFILE), FUSE_OPT_END }; static struct fuse_opt workaround_opts[] = { SSHFS_OPT("none", rename_workaround, 0), SSHFS_OPT("none", nodelay_workaround, 0), SSHFS_OPT("none", nodelaysrv_workaround, 0), SSHFS_OPT("none", truncate_workaround, 0), SSHFS_OPT("none", buflimit_workaround, 0), SSHFS_OPT("none", fstat_workaround, 0), SSHFS_OPT("all", rename_workaround, 1), SSHFS_OPT("all", nodelay_workaround, 1), SSHFS_OPT("all", nodelaysrv_workaround, 1), SSHFS_OPT("all", truncate_workaround, 1), SSHFS_OPT("all", buflimit_workaround, 1), SSHFS_OPT("all", fstat_workaround, 1), SSHFS_OPT("rename", rename_workaround, 1), SSHFS_OPT("norename", rename_workaround, 0), SSHFS_OPT("nodelay", nodelay_workaround, 1), SSHFS_OPT("nonodelay", nodelay_workaround, 0), SSHFS_OPT("nodelaysrv", nodelaysrv_workaround, 1), SSHFS_OPT("nonodelaysrv", nodelaysrv_workaround, 0), SSHFS_OPT("truncate", truncate_workaround, 1), SSHFS_OPT("notruncate", truncate_workaround, 0), SSHFS_OPT("buflimit", buflimit_workaround, 1), SSHFS_OPT("nobuflimit", buflimit_workaround, 0), SSHFS_OPT("fstat", fstat_workaround, 1), SSHFS_OPT("nofstat", fstat_workaround, 0), FUSE_OPT_END }; #define DEBUG(format, args...) \ do { if (sshfs.debug) fprintf(stderr, format, args); } while(0) static const char *type_name(uint8_t type) { switch(type) { case SSH_FXP_INIT: return "INIT"; case SSH_FXP_VERSION: return "VERSION"; case SSH_FXP_OPEN: return "OPEN"; case SSH_FXP_CLOSE: return "CLOSE"; case SSH_FXP_READ: return "READ"; case SSH_FXP_WRITE: return "WRITE"; case SSH_FXP_LSTAT: return "LSTAT"; case SSH_FXP_FSTAT: return "FSTAT"; case SSH_FXP_SETSTAT: return "SETSTAT"; case SSH_FXP_FSETSTAT: return "FSETSTAT"; case SSH_FXP_OPENDIR: return "OPENDIR"; case SSH_FXP_READDIR: return "READDIR"; case SSH_FXP_REMOVE: return "REMOVE"; case SSH_FXP_MKDIR: return "MKDIR"; case SSH_FXP_RMDIR: return "RMDIR"; case SSH_FXP_REALPATH: return "REALPATH"; case SSH_FXP_STAT: return "STAT"; case SSH_FXP_RENAME: return "RENAME"; case SSH_FXP_READLINK: return "READLINK"; case SSH_FXP_SYMLINK: return "SYMLINK"; case SSH_FXP_STATUS: return "STATUS"; case SSH_FXP_HANDLE: return "HANDLE"; case SSH_FXP_DATA: return "DATA"; case SSH_FXP_NAME: return "NAME"; case SSH_FXP_ATTRS: return "ATTRS"; case SSH_FXP_EXTENDED: return "EXTENDED"; case SSH_FXP_EXTENDED_REPLY: return "EXTENDED_REPLY"; default: return "???"; } } #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #define list_entry(ptr, type, member) \ container_of(ptr, type, member) static void list_init(struct list_head *head) { head->next = head; head->prev = head; } static void list_add(struct list_head *new, struct list_head *head) { struct list_head *prev = head; struct list_head *next = head->next; next->prev = new; new->next = next; new->prev = prev; prev->next = new; } static void list_del(struct list_head *entry) { struct list_head *prev = entry->prev; struct list_head *next = entry->next; next->prev = prev; prev->next = next; } static int list_empty(const struct list_head *head) { return head->next == head; } /* given a pointer to the uid/gid, and the mapping table, remap the * uid/gid, if necessary */ static inline int translate_id(uint32_t *id, GHashTable *map) { gpointer id_p; if (g_hash_table_lookup_extended(map, GUINT_TO_POINTER(*id), NULL, &id_p)) { *id = GPOINTER_TO_UINT(id_p); return 0; } switch (sshfs.nomap) { case NOMAP_ERROR: return -1; case NOMAP_IGNORE: return 0; default: fprintf(stderr, "internal error\n"); abort(); } } static inline void buf_init(struct buffer *buf, size_t size) { if (size) { buf->p = (uint8_t *) malloc(size); if (!buf->p) { fprintf(stderr, "sshfs: memory allocation failed\n"); abort(); } } else buf->p = NULL; buf->len = 0; buf->size = size; } static inline void buf_free(struct buffer *buf) { free(buf->p); } static inline void buf_finish(struct buffer *buf) { buf->len = buf->size; } static inline void buf_clear(struct buffer *buf) { buf_free(buf); buf_init(buf, 0); } static void buf_resize(struct buffer *buf, size_t len) { buf->size = (buf->len + len + 63) & ~31; buf->p = (uint8_t *) realloc(buf->p, buf->size); if (!buf->p) { fprintf(stderr, "sshfs: memory allocation failed\n"); abort(); } } static inline void buf_check_add(struct buffer *buf, size_t len) { if (buf->len + len > buf->size) buf_resize(buf, len); } #define _buf_add_mem(b, d, l) \ buf_check_add(b, l); \ memcpy(b->p + b->len, d, l); \ b->len += l; static inline void buf_add_mem(struct buffer *buf, const void *data, size_t len) { _buf_add_mem(buf, data, len); } static inline void buf_add_buf(struct buffer *buf, const struct buffer *bufa) { _buf_add_mem(buf, bufa->p, bufa->len); } static inline void buf_add_uint8(struct buffer *buf, uint8_t val) { _buf_add_mem(buf, &val, 1); } static inline void buf_add_uint32(struct buffer *buf, uint32_t val) { uint32_t nval = htonl(val); _buf_add_mem(buf, &nval, 4); } static inline void buf_add_uint64(struct buffer *buf, uint64_t val) { buf_add_uint32(buf, val >> 32); buf_add_uint32(buf, val & 0xffffffff); } static inline void buf_add_data(struct buffer *buf, const struct buffer *data) { buf_add_uint32(buf, data->len); buf_add_mem(buf, data->p, data->len); } static inline void buf_add_string(struct buffer *buf, const char *str) { struct buffer data; data.p = (uint8_t *) str; data.len = strlen(str); buf_add_data(buf, &data); } static inline void buf_add_path(struct buffer *buf, const char *path) { char *realpath; if (sshfs.base_path[0]) { if (path[1]) { if (sshfs.base_path[strlen(sshfs.base_path)-1] != '/') { realpath = g_strdup_printf("%s/%s", sshfs.base_path, path + 1); } else { realpath = g_strdup_printf("%s%s", sshfs.base_path, path + 1); } } else { realpath = g_strdup(sshfs.base_path); } } else { if (path[1]) realpath = g_strdup(path + 1); else realpath = g_strdup("."); } buf_add_string(buf, realpath); g_free(realpath); } static int buf_check_get(struct buffer *buf, size_t len) { if (buf->len + len > buf->size) { fprintf(stderr, "buffer too short\n"); return -1; } else return 0; } static inline int buf_get_mem(struct buffer *buf, void *data, size_t len) { if (buf_check_get(buf, len) == -1) return -1; memcpy(data, buf->p + buf->len, len); buf->len += len; return 0; } static inline int buf_get_uint8(struct buffer *buf, uint8_t *val) { return buf_get_mem(buf, val, 1); } static inline int buf_get_uint32(struct buffer *buf, uint32_t *val) { uint32_t nval; if (buf_get_mem(buf, &nval, 4) == -1) return -1; *val = ntohl(nval); return 0; } static inline int buf_get_uint64(struct buffer *buf, uint64_t *val) { uint32_t val1; uint32_t val2; if (buf_get_uint32(buf, &val1) == -1 || buf_get_uint32(buf, &val2) == -1) { return -1; } *val = ((uint64_t) val1 << 32) + val2; return 0; } static inline int buf_get_data(struct buffer *buf, struct buffer *data) { uint32_t len; if (buf_get_uint32(buf, &len) == -1 || len > buf->size - buf->len) return -1; buf_init(data, len + 1); data->size = len; if (buf_get_mem(buf, data->p, data->size) == -1) { buf_free(data); return -1; } return 0; } static inline int buf_get_string(struct buffer *buf, char **str) { struct buffer data; if (buf_get_data(buf, &data) == -1) return -1; data.p[data.size] = '\0'; *str = (char *) data.p; return 0; } static int buf_get_attrs(struct buffer *buf, struct stat *stbuf, int *flagsp) { uint32_t flags; uint64_t size = 0; uint32_t uid = 0; uint32_t gid = 0; uint32_t atime = 0; uint32_t mtime = 0; uint32_t mode = S_IFREG | 0777; if (buf_get_uint32(buf, &flags) == -1) return -EIO; if (flagsp) *flagsp = flags; if ((flags & SSH_FILEXFER_ATTR_SIZE) && buf_get_uint64(buf, &size) == -1) return -EIO; if ((flags & SSH_FILEXFER_ATTR_UIDGID) && (buf_get_uint32(buf, &uid) == -1 || buf_get_uint32(buf, &gid) == -1)) return -EIO; if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) && buf_get_uint32(buf, &mode) == -1) return -EIO; if ((flags & SSH_FILEXFER_ATTR_ACMODTIME)) { if (buf_get_uint32(buf, &atime) == -1 || buf_get_uint32(buf, &mtime) == -1) return -EIO; } if ((flags & SSH_FILEXFER_ATTR_EXTENDED)) { uint32_t extcount; unsigned i; if (buf_get_uint32(buf, &extcount) == -1) return -EIO; for (i = 0; i < extcount; i++) { struct buffer tmp; if (buf_get_data(buf, &tmp) == -1) return -EIO; buf_free(&tmp); if (buf_get_data(buf, &tmp) == -1) return -EIO; buf_free(&tmp); } } #ifdef __APPLE__ if (sshfs.remote_uid_detected) { if (uid == sshfs.remote_uid) uid = sshfs.local_uid; if (gid == sshfs.remote_gid) gid = sshfs.local_gid; } #else /* !__APPLE__ */ if (sshfs.remote_uid_detected && uid == sshfs.remote_uid) uid = sshfs.local_uid; #endif /* __APPLE__ */ if (sshfs.idmap == IDMAP_FILE && sshfs.uid_map) if (translate_id(&uid, sshfs.uid_map) == -1) return -EPERM; if (sshfs.idmap == IDMAP_FILE && sshfs.gid_map) if (translate_id(&gid, sshfs.gid_map) == -1) return -EPERM; memset(stbuf, 0, sizeof(struct stat)); stbuf->st_mode = mode; stbuf->st_nlink = 1; stbuf->st_size = size; if (sshfs.blksize) { stbuf->st_blksize = sshfs.blksize; stbuf->st_blocks = ((size + sshfs.blksize - 1) & ~((unsigned long long) sshfs.blksize - 1)) >> 9; } stbuf->st_uid = uid; stbuf->st_gid = gid; stbuf->st_atime = atime; stbuf->st_ctime = stbuf->st_mtime = mtime; return 0; } static int buf_get_statvfs(struct buffer *buf, struct statvfs *stbuf) { uint64_t bsize; uint64_t frsize; uint64_t blocks; uint64_t bfree; uint64_t bavail; uint64_t files; uint64_t ffree; uint64_t favail; uint64_t fsid; uint64_t flag; uint64_t namemax; if (buf_get_uint64(buf, &bsize) == -1 || buf_get_uint64(buf, &frsize) == -1 || buf_get_uint64(buf, &blocks) == -1 || buf_get_uint64(buf, &bfree) == -1 || buf_get_uint64(buf, &bavail) == -1 || buf_get_uint64(buf, &files) == -1 || buf_get_uint64(buf, &ffree) == -1 || buf_get_uint64(buf, &favail) == -1 || buf_get_uint64(buf, &fsid) == -1 || buf_get_uint64(buf, &flag) == -1 || buf_get_uint64(buf, &namemax) == -1) { return -1; } memset(stbuf, 0, sizeof(struct statvfs)); stbuf->f_bsize = bsize; stbuf->f_frsize = frsize; stbuf->f_blocks = blocks; stbuf->f_bfree = bfree; stbuf->f_bavail = bavail; stbuf->f_files = files; stbuf->f_ffree = ffree; stbuf->f_favail = favail; stbuf->f_namemax = namemax; return 0; } static int buf_get_entries(struct buffer *buf, fuse_cache_dirh_t h, fuse_cache_dirfil_t filler) { uint32_t count; unsigned i; if (buf_get_uint32(buf, &count) == -1) return -EIO; for (i = 0; i < count; i++) { int err = -1; char *name; char *longname; struct stat stbuf; if (buf_get_string(buf, &name) == -1) return -EIO; if (buf_get_string(buf, &longname) != -1) { free(longname); err = buf_get_attrs(buf, &stbuf, NULL); if (!err) { if (sshfs.follow_symlinks && S_ISLNK(stbuf.st_mode)) { stbuf.st_mode = 0; } filler(h, name, &stbuf); } } free(name); if (err) return err; } return 0; } static void ssh_add_arg(const char *arg) { if (fuse_opt_add_arg(&sshfs.ssh_args, arg) == -1) _exit(1); } #ifdef SSH_NODELAY_WORKAROUND static int do_ssh_nodelay_workaround(void) { #ifdef __APPLE__ char *oldpreload = getenv("DYLD_INSERT_LIBRARIES"); #else char *oldpreload = getenv("LD_PRELOAD"); #endif char *newpreload; char sopath[PATH_MAX]; int res; #ifdef __APPLE__ char *sshfs_program_path_base = NULL; if (!sshfs_program_path[0]) { goto nobundle; } sshfs_program_path_base = dirname(sshfs_program_path); if (!sshfs_program_path_base) { goto nobundle; } snprintf(sopath, sizeof(sopath), "%s/%s", sshfs_program_path_base, SSHNODELAY_SO); res = access(sopath, R_OK); if (res == -1) { goto nobundle; } goto pathok; nobundle: #endif /* __APPLE__ */ snprintf(sopath, sizeof(sopath), "%s/%s", LIBDIR, SSHNODELAY_SO); res = access(sopath, R_OK); if (res == -1) { char *s; if (!realpath(sshfs.progname, sopath)) return -1; s = strrchr(sopath, '/'); if (!s) s = sopath; else s++; if (s + strlen(SSHNODELAY_SO) >= sopath + sizeof(sopath)) return -1; strcpy(s, SSHNODELAY_SO); res = access(sopath, R_OK); if (res == -1) { fprintf(stderr, "sshfs: cannot find %s\n", SSHNODELAY_SO); return -1; } } #ifdef __APPLE__ pathok: #endif newpreload = g_strdup_printf("%s%s%s", oldpreload ? oldpreload : "", oldpreload ? " " : "", sopath); #ifdef __APPLE__ if (!newpreload || setenv("DYLD_INSERT_LIBRARIES", newpreload, 1) == -1) fprintf(stderr, "warning: failed set DYLD_INSERT_LIBRARIES for ssh nodelay workaround\n"); #else /* !__APPLE__ */ if (!newpreload || setenv("LD_PRELOAD", newpreload, 1) == -1) { fprintf(stderr, "warning: failed set LD_PRELOAD " "for ssh nodelay workaround\n"); } #endif /* __APPLE__ */ g_free(newpreload); return 0; } #endif static int pty_expect_loop(void) { int res; char buf[256]; const char *passwd_str = "assword:"; int timeout = 60 * 1000; /* 1min timeout for the prompt to appear */ int passwd_len = strlen(passwd_str); int len = 0; char c; while (1) { struct pollfd fds[2]; fds[0].fd = sshfs.rfd; fds[0].events = POLLIN; fds[1].fd = sshfs.ptyfd; fds[1].events = POLLIN; res = poll(fds, 2, timeout); if (res == -1) { perror("poll"); return -1; } if (res == 0) { fprintf(stderr, "Timeout waiting for prompt\n"); return -1; } if (fds[0].revents) { /* * Something happened on stdout of ssh, this * either means, that we are connected, or * that we are disconnected. In any case the * password doesn't matter any more. */ break; } res = read(sshfs.ptyfd, &c, 1); if (res == -1) { perror("read"); return -1; } if (res == 0) { fprintf(stderr, "EOF while waiting for prompt\n"); return -1; } buf[len] = c; len++; if (len == passwd_len) { if (memcmp(buf, passwd_str, passwd_len) == 0) { write(sshfs.ptyfd, sshfs.password, strlen(sshfs.password)); } memmove(buf, buf + 1, passwd_len - 1); len--; } } if (!sshfs.reconnect) { size_t size = getpagesize(); memset(sshfs.password, 0, size); munmap(sshfs.password, size); sshfs.password = NULL; } return 0; } static int pty_master(char **name) { int mfd; mfd = open("/dev/ptmx", O_RDWR | O_NOCTTY); if (mfd == -1) { perror("failed to open pty"); return -1; } if (grantpt(mfd) != 0) { perror("grantpt"); return -1; } if (unlockpt(mfd) != 0) { perror("unlockpt"); return -1; } *name = ptsname(mfd); return mfd; } static void replace_arg(char **argp, const char *newarg) { free(*argp); *argp = strdup(newarg); if (*argp == NULL) { fprintf(stderr, "sshfs: memory allocation failed\n"); abort(); } } static int start_ssh(void) { char *ptyname = NULL; int sockpair[2]; int pid; if (sshfs.password_stdin) { sshfs.ptyfd = pty_master(&ptyname); if (sshfs.ptyfd == -1) return -1; sshfs.ptyslavefd = open(ptyname, O_RDWR | O_NOCTTY); if (sshfs.ptyslavefd == -1) return -1; } if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) == -1) { perror("failed to create socket pair"); return -1; } sshfs.rfd = sockpair[0]; sshfs.wfd = sockpair[0]; pid = fork(); if (pid == -1) { perror("failed to fork"); close(sockpair[1]); return -1; } else if (pid == 0) { int devnull; #ifdef SSH_NODELAY_WORKAROUND if (sshfs.nodelay_workaround && do_ssh_nodelay_workaround() == -1) { fprintf(stderr, "warning: ssh nodelay workaround disabled\n"); } #endif if (sshfs.nodelaysrv_workaround) { int i; /* * Hack to work around missing TCP_NODELAY * setting in sshd */ for (i = 1; i < sshfs.ssh_args.argc; i++) { if (strcmp(sshfs.ssh_args.argv[i], "-x") == 0) { replace_arg(&sshfs.ssh_args.argv[i], "-X"); break; } } } devnull = open("/dev/null", O_WRONLY); if (dup2(sockpair[1], 0) == -1 || dup2(sockpair[1], 1) == -1) { perror("failed to redirect input/output"); _exit(1); } if (!sshfs.foreground && devnull != -1) dup2(devnull, 2); close(devnull); close(sockpair[0]); close(sockpair[1]); switch (fork()) { case -1: perror("failed to fork"); _exit(1); case 0: break; default: _exit(0); } chdir("/"); if (sshfs.password_stdin) { int sfd; setsid(); sfd = open(ptyname, O_RDWR); if (sfd == -1) { perror(ptyname); _exit(1); } close(sfd); close(sshfs.ptyslavefd); close(sshfs.ptyfd); } if (sshfs.debug) { int i; fprintf(stderr, "executing"); for (i = 0; i < sshfs.ssh_args.argc; i++) fprintf(stderr, " <%s>", sshfs.ssh_args.argv[i]); fprintf(stderr, "\n"); } execvp(sshfs.ssh_args.argv[0], sshfs.ssh_args.argv); fprintf(stderr, "failed to execute '%s': %s\n", sshfs.ssh_args.argv[0], strerror(errno)); _exit(1); } waitpid(pid, NULL, 0); close(sockpair[1]); return 0; } static int connect_slave() { sshfs.rfd = STDIN_FILENO; sshfs.wfd = STDOUT_FILENO; return 0; } static int connect_to(char *host, char *port) { int err; int sock; int opt; struct addrinfo *ai; struct addrinfo hint; memset(&hint, 0, sizeof(hint)); hint.ai_family = PF_INET; hint.ai_socktype = SOCK_STREAM; err = getaddrinfo(host, port, &hint, &ai); if (err) { fprintf(stderr, "failed to resolve %s:%s: %s\n", host, port, gai_strerror(err)); return -1; } sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock == -1) { perror("failed to create socket"); return -1; } err = connect(sock, ai->ai_addr, ai->ai_addrlen); if (err == -1) { perror("failed to connect"); return -1; } opt = 1; err = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); if (err == -1) perror("warning: failed to set TCP_NODELAY"); freeaddrinfo(ai); sshfs.rfd = sock; sshfs.wfd = sock; return 0; } static int do_write(struct iovec *iov, size_t count) { int res; while (count) { res = writev(sshfs.wfd, iov, count); if (res == -1) { perror("write"); return -1; } else if (res == 0) { fprintf(stderr, "zero write\n"); return -1; } do { if ((unsigned) res < iov->iov_len) { iov->iov_len -= res; iov->iov_base += res; break; } else { res -= iov->iov_len; count --; iov ++; } } while(count); } return 0; } static uint32_t sftp_get_id(void) { static uint32_t idctr; return idctr++; } static void buf_to_iov(const struct buffer *buf, struct iovec *iov) { iov->iov_base = buf->p; iov->iov_len = buf->len; } static size_t iov_length(const struct iovec *iov, unsigned long nr_segs) { unsigned long seg; size_t ret = 0; for (seg = 0; seg < nr_segs; seg++) ret += iov[seg].iov_len; return ret; } #define SFTP_MAX_IOV 3 static int sftp_send_iov(uint8_t type, uint32_t id, struct iovec iov[], size_t count) { int res; struct buffer buf; struct iovec iovout[SFTP_MAX_IOV]; unsigned i; unsigned nout = 0; assert(count <= SFTP_MAX_IOV - 1); buf_init(&buf, 9); buf_add_uint32(&buf, iov_length(iov, count) + 5); buf_add_uint8(&buf, type); buf_add_uint32(&buf, id); buf_to_iov(&buf, &iovout[nout++]); for (i = 0; i < count; i++) iovout[nout++] = iov[i]; pthread_mutex_lock(&sshfs.lock_write); res = do_write(iovout, nout); pthread_mutex_unlock(&sshfs.lock_write); buf_free(&buf); return res; } static int do_read(struct buffer *buf) { int res; uint8_t *p = buf->p; size_t size = buf->size; while (size) { res = read(sshfs.rfd, p, size); if (res == -1) { perror("read"); return -1; } else if (res == 0) { fprintf(stderr, "remote host has disconnected\n"); return -1; } size -= res; p += res; } return 0; } static int sftp_read(uint8_t *type, struct buffer *buf) { int res; struct buffer buf2; uint32_t len; buf_init(&buf2, 5); res = do_read(&buf2); if (res != -1) { if (buf_get_uint32(&buf2, &len) == -1) return -1; if (len > MAX_REPLY_LEN) { fprintf(stderr, "reply len too large: %u\n", len); return -1; } if (buf_get_uint8(&buf2, type) == -1) return -1; buf_init(buf, len - 1); res = do_read(buf); } buf_free(&buf2); return res; } static void request_free(struct request *req) { buf_free(&req->reply); sem_destroy(&req->ready); g_free(req); } static void chunk_free(struct read_chunk *chunk) { while (!list_empty(&chunk->reqs)) { struct read_req *rreq; rreq = list_entry(chunk->reqs.prev, struct read_req, list); list_del(&rreq->list); buf_free(&rreq->data); g_free(rreq); } g_free(chunk); } static void chunk_put(struct read_chunk *chunk) { if (chunk) { chunk->refs--; if (!chunk->refs) chunk_free(chunk); } } static void chunk_put_locked(struct read_chunk *chunk) { pthread_mutex_lock(&sshfs.lock); chunk_put(chunk); pthread_mutex_unlock(&sshfs.lock); } static int clean_req(void *key_, struct request *req) { (void) key_; req->error = -EIO; if (req->want_reply) sem_post(&req->ready); else { if (req->end_func) req->end_func(req); request_free(req); } return TRUE; } static int process_one_request(void) { int res; struct buffer buf; uint8_t type; struct request *req; uint32_t id; buf_init(&buf, 0); res = sftp_read(&type, &buf); if (res == -1) return -1; if (buf_get_uint32(&buf, &id) == -1) return -1; pthread_mutex_lock(&sshfs.lock); req = (struct request *) g_hash_table_lookup(sshfs.reqtab, GUINT_TO_POINTER(id)); if (req == NULL) fprintf(stderr, "request %i not found\n", id); else { int was_over; was_over = sshfs.outstanding_len > sshfs.max_outstanding_len; sshfs.outstanding_len -= req->len; if (was_over && sshfs.outstanding_len <= sshfs.max_outstanding_len) { pthread_cond_broadcast(&sshfs.outstanding_cond); } g_hash_table_remove(sshfs.reqtab, GUINT_TO_POINTER(id)); } pthread_mutex_unlock(&sshfs.lock); if (req != NULL) { if (sshfs.debug) { struct timeval now; unsigned int difftime; unsigned msgsize = buf.size + 5; gettimeofday(&now, NULL); difftime = (now.tv_sec - req->start.tv_sec) * 1000; difftime += (now.tv_usec - req->start.tv_usec) / 1000; DEBUG(" [%05i] %14s %8ubytes (%ims)\n", id, type_name(type), msgsize, difftime); if (difftime < sshfs.min_rtt || !sshfs.num_received) sshfs.min_rtt = difftime; if (difftime > sshfs.max_rtt) sshfs.max_rtt = difftime; sshfs.total_rtt += difftime; sshfs.num_received++; sshfs.bytes_received += msgsize; } req->reply = buf; req->reply_type = type; req->replied = 1; if (req->want_reply) sem_post(&req->ready); else { if (req->end_func) { pthread_mutex_lock(&sshfs.lock); req->end_func(req); pthread_mutex_unlock(&sshfs.lock); } request_free(req); } } else buf_free(&buf); return 0; } static void close_conn(void) { close(sshfs.rfd); if (sshfs.rfd != sshfs.wfd) close(sshfs.wfd); sshfs.rfd = -1; sshfs.wfd = -1; if (sshfs.ptyfd != -1) { close(sshfs.ptyfd); sshfs.ptyfd = -1; } if (sshfs.ptyslavefd != -1) { close(sshfs.ptyslavefd); sshfs.ptyslavefd = -1; } } static void *process_requests(void *data_) { (void) data_; while (1) { if (process_one_request() == -1) break; } pthread_mutex_lock(&sshfs.lock); sshfs.processing_thread_started = 0; close_conn(); g_hash_table_foreach_remove(sshfs.reqtab, (GHRFunc) clean_req, NULL); sshfs.connver ++; sshfs.outstanding_len = 0; pthread_cond_broadcast(&sshfs.outstanding_cond); pthread_mutex_unlock(&sshfs.lock); if (!sshfs.reconnect) { /* harakiri */ kill(getpid(), SIGTERM); } return NULL; } static int sftp_init_reply_ok(struct buffer *buf, uint32_t *version) { uint32_t len; uint8_t type; if (buf_get_uint32(buf, &len) == -1) return -1; if (len < 5 || len > MAX_REPLY_LEN) return 1; if (buf_get_uint8(buf, &type) == -1) return -1; if (type != SSH_FXP_VERSION) return 1; if (buf_get_uint32(buf, version) == -1) return -1; DEBUG("Server version: %u\n", *version); if (len > 5) { struct buffer buf2; buf_init(&buf2, len - 5); if (do_read(&buf2) == -1) { buf_free(&buf2); return -1; } do { char *ext; char *extdata; if (buf_get_string(&buf2, &ext) == -1 || buf_get_string(&buf2, &extdata) == -1) { buf_free(&buf2); return -1; } DEBUG("Extension: %s <%s>\n", ext, extdata); if (strcmp(ext, SFTP_EXT_POSIX_RENAME) == 0 && strcmp(extdata, "1") == 0) { sshfs.ext_posix_rename = 1; sshfs.rename_workaround = 0; } if (strcmp(ext, SFTP_EXT_STATVFS) == 0 && strcmp(extdata, "2") == 0) sshfs.ext_statvfs = 1; if (strcmp(ext, SFTP_EXT_HARDLINK) == 0 && strcmp(extdata, "1") == 0) sshfs.ext_hardlink = 1; if (strcmp(ext, SFTP_EXT_FSYNC) == 0 && strcmp(extdata, "1") == 0) sshfs.ext_fsync = 1; } while (buf2.len < buf2.size); buf_free(&buf2); } return 0; } static int sftp_find_init_reply(uint32_t *version) { int res; struct buffer buf; buf_init(&buf, 9); res = do_read(&buf); while (res != -1) { struct buffer buf2; res = sftp_init_reply_ok(&buf, version); if (res <= 0) break; /* Iterate over any rubbish until the version reply is found */ DEBUG("%c", *buf.p); memmove(buf.p, buf.p + 1, buf.size - 1); buf.len = 0; buf2.p = buf.p + buf.size - 1; buf2.size = 1; res = do_read(&buf2); } buf_free(&buf); return res; } static int sftp_init() { int res = -1; uint32_t version = 0; struct buffer buf; buf_init(&buf, 0); if (sftp_send_iov(SSH_FXP_INIT, PROTO_VERSION, NULL, 0) == -1) goto out; if (sshfs.password_stdin && pty_expect_loop() == -1) goto out; if (sftp_find_init_reply(&version) == -1) goto out; sshfs.server_version = version; if (version > PROTO_VERSION) { fprintf(stderr, "Warning: server uses version: %i, we support: %i\n", version, PROTO_VERSION); } res = 0; out: buf_free(&buf); return res; } static int sftp_error_to_errno(uint32_t error) { switch (error) { case SSH_FX_OK: return 0; case SSH_FX_NO_SUCH_FILE: return ENOENT; case SSH_FX_PERMISSION_DENIED: return EACCES; case SSH_FX_FAILURE: return EPERM; case SSH_FX_BAD_MESSAGE: return EBADMSG; case SSH_FX_NO_CONNECTION: return ENOTCONN; case SSH_FX_CONNECTION_LOST: return ECONNABORTED; case SSH_FX_OP_UNSUPPORTED: return EOPNOTSUPP; default: return EIO; } } static void sftp_detect_uid() { int flags; uint32_t id = sftp_get_id(); uint32_t replid; uint8_t type; struct buffer buf; struct stat stbuf; struct iovec iov[1]; buf_init(&buf, 5); buf_add_string(&buf, "."); buf_to_iov(&buf, &iov[0]); if (sftp_send_iov(SSH_FXP_STAT, id, iov, 1) == -1) goto out; buf_clear(&buf); if (sftp_read(&type, &buf) == -1) goto out; if (type != SSH_FXP_ATTRS && type != SSH_FXP_STATUS) { fprintf(stderr, "protocol error\n"); goto out; } if (buf_get_uint32(&buf, &replid) == -1) goto out; if (replid != id) { fprintf(stderr, "bad reply ID\n"); goto out; } if (type == SSH_FXP_STATUS) { uint32_t serr; if (buf_get_uint32(&buf, &serr) == -1) goto out; fprintf(stderr, "failed to stat home directory (%i)\n", serr); goto out; } if (buf_get_attrs(&buf, &stbuf, &flags) != 0) goto out; if (!(flags & SSH_FILEXFER_ATTR_UIDGID)) goto out; sshfs.remote_uid = stbuf.st_uid; sshfs.local_uid = getuid(); #ifdef __APPLE__ sshfs.remote_gid = stbuf.st_gid; sshfs.local_gid = getgid(); #endif sshfs.remote_uid_detected = 1; DEBUG("remote_uid = %i\n", sshfs.remote_uid); out: if (!sshfs.remote_uid_detected) fprintf(stderr, "failed to detect remote user ID\n"); buf_free(&buf); } static int sftp_check_root(const char *base_path) { int flags; uint32_t id = sftp_get_id(); uint32_t replid; uint8_t type; struct buffer buf; struct stat stbuf; struct iovec iov[1]; int err = -1; const char *remote_dir = base_path[0] ? base_path : "."; buf_init(&buf, 0); buf_add_string(&buf, remote_dir); buf_to_iov(&buf, &iov[0]); if (sftp_send_iov(SSH_FXP_LSTAT, id, iov, 1) == -1) goto out; buf_clear(&buf); if (sftp_read(&type, &buf) == -1) goto out; if (type != SSH_FXP_ATTRS && type != SSH_FXP_STATUS) { fprintf(stderr, "protocol error\n"); goto out; } if (buf_get_uint32(&buf, &replid) == -1) goto out; if (replid != id) { fprintf(stderr, "bad reply ID\n"); goto out; } if (type == SSH_FXP_STATUS) { uint32_t serr; if (buf_get_uint32(&buf, &serr) == -1) goto out; fprintf(stderr, "%s:%s: %s\n", sshfs.host, remote_dir, strerror(sftp_error_to_errno(serr))); goto out; } int err2 = buf_get_attrs(&buf, &stbuf, &flags); if (err2) { err = err2; goto out; } if (!(flags & SSH_FILEXFER_ATTR_PERMISSIONS)) goto out; if (S_ISDIR(sshfs.mnt_mode) && !S_ISDIR(stbuf.st_mode)) { fprintf(stderr, "%s:%s: Not a directory\n", sshfs.host, remote_dir); goto out; } if ((sshfs.mnt_mode ^ stbuf.st_mode) & S_IFMT) { fprintf(stderr, "%s:%s: type of file differs from mountpoint\n", sshfs.host, remote_dir); goto out; } err = 0; out: buf_free(&buf); return err; } static int connect_remote(void) { int err; if (sshfs.slave) err = connect_slave(); else if (sshfs.directport) err = connect_to(sshfs.host, sshfs.directport); else err = start_ssh(); if (!err) err = sftp_init(); if (err) close_conn(); else sshfs.num_connect++; return err; } static int start_processing_thread(void) { int err; pthread_t thread_id; sigset_t oldset; sigset_t newset; if (sshfs.processing_thread_started) return 0; if (sshfs.rfd == -1) { err = connect_remote(); if (err) return -EIO; } if (sshfs.detect_uid) { sftp_detect_uid(); sshfs.detect_uid = 0; } sigemptyset(&newset); sigaddset(&newset, SIGTERM); sigaddset(&newset, SIGINT); sigaddset(&newset, SIGHUP); sigaddset(&newset, SIGQUIT); pthread_sigmask(SIG_BLOCK, &newset, &oldset); err = pthread_create(&thread_id, NULL, process_requests, NULL); if (err) { fprintf(stderr, "failed to create thread: %s\n", strerror(err)); return -EIO; } pthread_detach(thread_id); pthread_sigmask(SIG_SETMASK, &oldset, NULL); sshfs.processing_thread_started = 1; return 0; } #if FUSE_VERSION >= 26 static void *sshfs_init(struct fuse_conn_info *conn) #else static void *sshfs_init(void) #endif { #if FUSE_VERSION >= 26 /* Readahead should be done by kernel or sshfs but not both */ if (conn->async_read) sshfs.sync_read = 1; #endif if (!sshfs.delay_connect) start_processing_thread(); return NULL; } static int sftp_request_wait(struct request *req, uint8_t type, uint8_t expect_type, struct buffer *outbuf) { int err; if (req->error) { err = req->error; goto out; } while (sem_wait(&req->ready)); if (req->error) { err = req->error; goto out; } err = -EIO; if (req->reply_type != expect_type && req->reply_type != SSH_FXP_STATUS) { fprintf(stderr, "protocol error\n"); goto out; } if (req->reply_type == SSH_FXP_STATUS) { uint32_t serr; if (buf_get_uint32(&req->reply, &serr) == -1) goto out; switch (serr) { case SSH_FX_OK: if (expect_type == SSH_FXP_STATUS) err = 0; else err = -EIO; break; case SSH_FX_EOF: if (type == SSH_FXP_READ || type == SSH_FXP_READDIR) err = MY_EOF; else err = -EIO; break; case SSH_FX_FAILURE: if (type == SSH_FXP_RMDIR) err = -ENOTEMPTY; else err = -EPERM; break; default: err = -sftp_error_to_errno(serr); } } else { buf_init(outbuf, req->reply.size - req->reply.len); buf_get_mem(&req->reply, outbuf->p, outbuf->size); err = 0; } out: if (req->end_func) { pthread_mutex_lock(&sshfs.lock); req->end_func(req); pthread_mutex_unlock(&sshfs.lock); } request_free(req); return err; } static int sftp_request_send(uint8_t type, struct iovec *iov, size_t count, request_func begin_func, request_func end_func, int want_reply, void *data, struct request **reqp) { int err; uint32_t id; struct request *req = g_new0(struct request, 1); req->want_reply = want_reply; req->end_func = end_func; req->data = data; sem_init(&req->ready, 0, 0); buf_init(&req->reply, 0); pthread_mutex_lock(&sshfs.lock); if (begin_func) begin_func(req); id = sftp_get_id(); req->id = id; err = start_processing_thread(); if (err) { pthread_mutex_unlock(&sshfs.lock); goto out; } req->len = iov_length(iov, count) + 9; sshfs.outstanding_len += req->len; while (sshfs.outstanding_len > sshfs.max_outstanding_len) pthread_cond_wait(&sshfs.outstanding_cond, &sshfs.lock); g_hash_table_insert(sshfs.reqtab, GUINT_TO_POINTER(id), req); if (sshfs.debug) { gettimeofday(&req->start, NULL); sshfs.num_sent++; sshfs.bytes_sent += req->len; } DEBUG("[%05i] %s\n", id, type_name(type)); pthread_mutex_unlock(&sshfs.lock); err = -EIO; if (sftp_send_iov(type, id, iov, count) == -1) { gboolean rmed; pthread_mutex_lock(&sshfs.lock); rmed = g_hash_table_remove(sshfs.reqtab, GUINT_TO_POINTER(id)); pthread_mutex_unlock(&sshfs.lock); if (!rmed && !want_reply) { /* request already freed */ return err; } goto out; } if (want_reply) *reqp = req; return 0; out: req->error = err; if (!want_reply) sftp_request_wait(req, type, 0, NULL); else *reqp = req; return err; } static int sftp_request_iov(uint8_t type, struct iovec *iov, size_t count, uint8_t expect_type, struct buffer *outbuf) { int err; struct request *req; err = sftp_request_send(type, iov, count, NULL, NULL, expect_type, NULL, &req); if (expect_type == 0) return err; return sftp_request_wait(req, type, expect_type, outbuf); } static int sftp_request(uint8_t type, const struct buffer *buf, uint8_t expect_type, struct buffer *outbuf) { struct iovec iov; buf_to_iov(buf, &iov); return sftp_request_iov(type, &iov, 1, expect_type, outbuf); } static int sshfs_getattr(const char *path, struct stat *stbuf) { int err; struct buffer buf; struct buffer outbuf; buf_init(&buf, 0); buf_add_path(&buf, path); err = sftp_request(sshfs.follow_symlinks ? SSH_FXP_STAT : SSH_FXP_LSTAT, &buf, SSH_FXP_ATTRS, &outbuf); if (!err) { err = buf_get_attrs(&outbuf, stbuf, NULL); buf_free(&outbuf); } buf_free(&buf); return err; } static int sshfs_access(const char *path, int mask) { struct stat stbuf; int err = 0; if (mask & X_OK) { err = sshfs.op->getattr(path, &stbuf); if (!err) { if (S_ISREG(stbuf.st_mode) && !(stbuf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) err = -EACCES; } } return err; } static int count_components(const char *p) { int ctr; for (; *p == '/'; p++); for (ctr = 0; *p; ctr++) { for (; *p && *p != '/'; p++); for (; *p == '/'; p++); } return ctr; } static void strip_common(const char **sp, const char **tp) { const char *s = *sp; const char *t = *tp; do { for (; *s == '/'; s++); for (; *t == '/'; t++); *tp = t; *sp = s; for (; *s == *t && *s && *s != '/'; s++, t++); } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t)); } static void transform_symlink(const char *path, char **linkp) { const char *l = *linkp; const char *b = sshfs.base_path; char *newlink; char *s; int dotdots; int i; if (l[0] != '/' || b[0] != '/') return; strip_common(&l, &b); if (*b) return; strip_common(&l, &path); dotdots = count_components(path); if (!dotdots) return; dotdots--; newlink = malloc(dotdots * 3 + strlen(l) + 2); if (!newlink) { fprintf(stderr, "sshfs: memory allocation failed\n"); abort(); } for (s = newlink, i = 0; i < dotdots; i++, s += 3) strcpy(s, "../"); if (l[0]) strcpy(s, l); else if (!dotdots) strcpy(s, "."); else s[0] = '\0'; free(*linkp); *linkp = newlink; } static int sshfs_readlink(const char *path, char *linkbuf, size_t size) { int err; struct buffer buf; struct buffer name; assert(size > 0); if (sshfs.server_version < 3) return -EPERM; buf_init(&buf, 0); buf_add_path(&buf, path); err = sftp_request(SSH_FXP_READLINK, &buf, SSH_FXP_NAME, &name); if (!err) { uint32_t count; char *link; err = -EIO; if(buf_get_uint32(&name, &count) != -1 && count == 1 && buf_get_string(&name, &link) != -1) { if (sshfs.transform_symlinks) transform_symlink(path, &link); strncpy(linkbuf, link, size - 1); linkbuf[size - 1] = '\0'; free(link); err = 0; } buf_free(&name); } buf_free(&buf); return err; } static int sftp_readdir_send(struct request **req, struct buffer *handle) { struct iovec iov; buf_to_iov(handle, &iov); return sftp_request_send(SSH_FXP_READDIR, &iov, 1, NULL, NULL, SSH_FXP_NAME, NULL, req); } static int sshfs_req_pending(struct request *req) { if (g_hash_table_lookup(sshfs.reqtab, GUINT_TO_POINTER(req->id))) return 1; else return 0; } static int sftp_readdir_async(struct buffer *handle, fuse_cache_dirh_t h, fuse_cache_dirfil_t filler) { int err = 0; int outstanding = 0; int max = READDIR_START; GList *list = NULL; int done = 0; while (!done || outstanding) { struct request *req; struct buffer name; int tmperr; while (!done && outstanding < max) { tmperr = sftp_readdir_send(&req, handle); if (tmperr && !done) { err = tmperr; done = 1; break; } list = g_list_append(list, req); outstanding++; } if (outstanding) { GList *first; /* wait for response to next request */ first = g_list_first(list); req = first->data; list = g_list_delete_link(list, first); outstanding--; if (done) { pthread_mutex_lock(&sshfs.lock); if (sshfs_req_pending(req)) req->want_reply = 0; pthread_mutex_unlock(&sshfs.lock); if (!req->want_reply) continue; } tmperr = sftp_request_wait(req, SSH_FXP_READDIR, SSH_FXP_NAME, &name); if (tmperr && !done) { err = tmperr; if (err == MY_EOF) err = 0; done = 1; } if (!done) { err = buf_get_entries(&name, h, filler); buf_free(&name); /* increase number of outstanding requests */ if (max < READDIR_MAX) max++; if (err) done = 1; } } } assert(list == NULL); return err; } static int sftp_readdir_sync(struct buffer *handle, fuse_cache_dirh_t h, fuse_cache_dirfil_t filler) { int err; do { struct buffer name; err = sftp_request(SSH_FXP_READDIR, handle, SSH_FXP_NAME, &name); if (!err) { err = buf_get_entries(&name, h, filler); buf_free(&name); } } while (!err); if (err == MY_EOF) err = 0; return err; } static int sshfs_getdir(const char *path, fuse_cache_dirh_t h, fuse_cache_dirfil_t filler) { int err; struct buffer buf; struct buffer handle; buf_init(&buf, 0); buf_add_path(&buf, path); err = sftp_request(SSH_FXP_OPENDIR, &buf, SSH_FXP_HANDLE, &handle); if (!err) { int err2; buf_finish(&handle); if (sshfs.sync_readdir) err = sftp_readdir_sync(&handle, h, filler); else err = sftp_readdir_async(&handle, h, filler); err2 = sftp_request(SSH_FXP_CLOSE, &handle, 0, NULL); if (!err) err = err2; buf_free(&handle); } buf_free(&buf); return err; } static int sshfs_mkdir(const char *path, mode_t mode) { int err; struct buffer buf; buf_init(&buf, 0); buf_add_path(&buf, path); buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); buf_add_uint32(&buf, mode); err = sftp_request(SSH_FXP_MKDIR, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } static int sshfs_mknod(const char *path, mode_t mode, dev_t rdev) { int err; struct buffer buf; struct buffer handle; (void) rdev; if ((mode & S_IFMT) != S_IFREG) return -EPERM; buf_init(&buf, 0); buf_add_path(&buf, path); buf_add_uint32(&buf, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_EXCL); buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); buf_add_uint32(&buf, mode); err = sftp_request(SSH_FXP_OPEN, &buf, SSH_FXP_HANDLE, &handle); if (!err) { int err2; buf_finish(&handle); err2 = sftp_request(SSH_FXP_CLOSE, &handle, SSH_FXP_STATUS, NULL); if (!err) err = err2; buf_free(&handle); } buf_free(&buf); return err; } static int sshfs_symlink(const char *from, const char *to) { int err; struct buffer buf; if (sshfs.server_version < 3) return -EPERM; /* openssh sftp server doesn't follow standard: link target and link name are mixed up, so we must also be non-standard :( */ buf_init(&buf, 0); buf_add_string(&buf, from); buf_add_path(&buf, to); err = sftp_request(SSH_FXP_SYMLINK, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } static int sshfs_unlink(const char *path) { int err; struct buffer buf; buf_init(&buf, 0); buf_add_path(&buf, path); err = sftp_request(SSH_FXP_REMOVE, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } static int sshfs_rmdir(const char *path) { int err; struct buffer buf; buf_init(&buf, 0); buf_add_path(&buf, path); err = sftp_request(SSH_FXP_RMDIR, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } static int sshfs_do_rename(const char *from, const char *to) { int err; struct buffer buf; buf_init(&buf, 0); buf_add_path(&buf, from); buf_add_path(&buf, to); err = sftp_request(SSH_FXP_RENAME, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } static int sshfs_ext_posix_rename(const char *from, const char *to) { int err; struct buffer buf; buf_init(&buf, 0); buf_add_string(&buf, SFTP_EXT_POSIX_RENAME); buf_add_path(&buf, from); buf_add_path(&buf, to); err = sftp_request(SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } static void random_string(char *str, int length) { int i; for (i = 0; i < length; i++) *str++ = (char)('0' + rand_r(&sshfs.randseed) % 10); *str = '\0'; } static int sshfs_rename(const char *from, const char *to) { int err; if (sshfs.ext_posix_rename) err = sshfs_ext_posix_rename(from, to); else err = sshfs_do_rename(from, to); if (err == -EPERM && sshfs.rename_workaround) { size_t tolen = strlen(to); if (tolen + RENAME_TEMP_CHARS < PATH_MAX) { int tmperr; char totmp[PATH_MAX]; strcpy(totmp, to); random_string(totmp + tolen, RENAME_TEMP_CHARS); tmperr = sshfs_do_rename(to, totmp); if (!tmperr) { err = sshfs_do_rename(from, to); if (!err) err = sshfs_unlink(totmp); else sshfs_do_rename(totmp, to); } } } return err; } static int sshfs_link(const char *from, const char *to) { int err = -ENOSYS; if (sshfs.ext_hardlink && !sshfs.disable_hardlink) { struct buffer buf; buf_init(&buf, 0); buf_add_string(&buf, SFTP_EXT_HARDLINK); buf_add_path(&buf, from); buf_add_path(&buf, to); err = sftp_request(SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); } return err; } static int sshfs_chmod(const char *path, mode_t mode) { int err; struct buffer buf; buf_init(&buf, 0); buf_add_path(&buf, path); buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); buf_add_uint32(&buf, mode); /* FIXME: really needs LSETSTAT extension (debian Bug#640038) */ err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } static int sshfs_chown(const char *path, uid_t uid, gid_t gid) { int err; struct buffer buf; #ifdef __APPLE__ if (sshfs.remote_uid_detected) { if (uid == sshfs.local_uid) uid = sshfs.remote_uid; if (gid == sshfs.local_gid) gid = sshfs.remote_gid; } #else /* !__APPLE__ */ if (sshfs.remote_uid_detected && uid == sshfs.local_uid) uid = sshfs.remote_uid; #endif /* __APPLE__ */ if (sshfs.idmap == IDMAP_FILE && sshfs.r_uid_map) if(translate_id(&uid, sshfs.r_uid_map) == -1) return -EPERM; if (sshfs.idmap == IDMAP_FILE && sshfs.r_gid_map) if (translate_id(&gid, sshfs.r_gid_map) == -1) return -EPERM; buf_init(&buf, 0); buf_add_path(&buf, path); buf_add_uint32(&buf, SSH_FILEXFER_ATTR_UIDGID); buf_add_uint32(&buf, uid); buf_add_uint32(&buf, gid); err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } static int sshfs_truncate_workaround(const char *path, off_t size, struct fuse_file_info *fi); static void sshfs_inc_modifver(void) { pthread_mutex_lock(&sshfs.lock); sshfs.modifver++; pthread_mutex_unlock(&sshfs.lock); } static int sshfs_truncate(const char *path, off_t size) { int err; struct buffer buf; sshfs_inc_modifver(); if (size == 0 || sshfs.truncate_workaround) return sshfs_truncate_workaround(path, size, NULL); buf_init(&buf, 0); buf_add_path(&buf, path); buf_add_uint32(&buf, SSH_FILEXFER_ATTR_SIZE); buf_add_uint64(&buf, size); err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } static int sshfs_utime(const char *path, struct utimbuf *ubuf) { int err; struct buffer buf; buf_init(&buf, 0); buf_add_path(&buf, path); buf_add_uint32(&buf, SSH_FILEXFER_ATTR_ACMODTIME); buf_add_uint32(&buf, ubuf->actime); buf_add_uint32(&buf, ubuf->modtime); err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } static inline int sshfs_file_is_conn(struct sshfs_file *sf) { int ret; pthread_mutex_lock(&sshfs.lock); ret = (sf->connver == sshfs.connver); pthread_mutex_unlock(&sshfs.lock); return ret; } static int sshfs_open_common(const char *path, mode_t mode, struct fuse_file_info *fi) { int err; int err2; struct buffer buf; struct buffer outbuf; struct stat stbuf; struct sshfs_file *sf; struct request *open_req; uint32_t pflags = 0; struct iovec iov; uint8_t type; uint64_t wrctr = cache_get_write_ctr(); if ((fi->flags & O_ACCMODE) == O_RDONLY) pflags = SSH_FXF_READ; else if((fi->flags & O_ACCMODE) == O_WRONLY) pflags = SSH_FXF_WRITE; else if ((fi->flags & O_ACCMODE) == O_RDWR) pflags = SSH_FXF_READ | SSH_FXF_WRITE; else return -EINVAL; if (fi->flags & O_CREAT) pflags |= SSH_FXF_CREAT; if (fi->flags & O_EXCL) pflags |= SSH_FXF_EXCL; if (fi->flags & O_TRUNC) pflags |= SSH_FXF_TRUNC; sf = g_new0(struct sshfs_file, 1); list_init(&sf->write_reqs); pthread_cond_init(&sf->write_finished, NULL); /* Assume random read after open */ sf->is_seq = 0; sf->refs = 1; sf->next_pos = 0; pthread_mutex_lock(&sshfs.lock); sf->modifver= sshfs.modifver; sf->connver = sshfs.connver; pthread_mutex_unlock(&sshfs.lock); buf_init(&buf, 0); buf_add_path(&buf, path); buf_add_uint32(&buf, pflags); buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); buf_add_uint32(&buf, mode); buf_to_iov(&buf, &iov); sftp_request_send(SSH_FXP_OPEN, &iov, 1, NULL, NULL, 1, NULL, &open_req); buf_clear(&buf); buf_add_path(&buf, path); type = sshfs.follow_symlinks ? SSH_FXP_STAT : SSH_FXP_LSTAT; err2 = sftp_request(type, &buf, SSH_FXP_ATTRS, &outbuf); if (!err2) { err2 = buf_get_attrs(&outbuf, &stbuf, NULL); buf_free(&outbuf); } err = sftp_request_wait(open_req, SSH_FXP_OPEN, SSH_FXP_HANDLE, &sf->handle); if (!err && err2) { buf_finish(&sf->handle); sftp_request(SSH_FXP_CLOSE, &sf->handle, 0, NULL); buf_free(&sf->handle); err = err2; } if (!err) { cache_add_attr(path, &stbuf, wrctr); buf_finish(&sf->handle); fi->fh = (unsigned long) sf; } else { cache_invalidate(path); g_free(sf); } buf_free(&buf); return err; } static int sshfs_open(const char *path, struct fuse_file_info *fi) { return sshfs_open_common(path, 0, fi); } static inline struct sshfs_file *get_sshfs_file(struct fuse_file_info *fi) { return (struct sshfs_file *) (uintptr_t) fi->fh; } static int sshfs_flush(const char *path, struct fuse_file_info *fi) { int err; struct sshfs_file *sf = get_sshfs_file(fi); struct list_head write_reqs; struct list_head *curr_list; if (!sshfs_file_is_conn(sf)) return -EIO; if (sshfs.sync_write) return 0; (void) path; pthread_mutex_lock(&sshfs.lock); if (!list_empty(&sf->write_reqs)) { curr_list = sf->write_reqs.prev; list_del(&sf->write_reqs); list_init(&sf->write_reqs); list_add(&write_reqs, curr_list); while (!list_empty(&write_reqs)) pthread_cond_wait(&sf->write_finished, &sshfs.lock); } err = sf->write_error; sf->write_error = 0; pthread_mutex_unlock(&sshfs.lock); return err; } static int sshfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi) { int err; (void) isdatasync; if (err = sshfs_flush(path, fi)) return err; if (!sshfs.ext_fsync) return err; { struct buffer buf; struct sshfs_file *sf = get_sshfs_file(fi); buf_init(&buf, 0); buf_add_string(&buf, SFTP_EXT_FSYNC); buf_add_buf(&buf, &sf->handle); err = sftp_request(SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } } static void sshfs_file_put(struct sshfs_file *sf) { sf->refs--; if (!sf->refs) g_free(sf); } static void sshfs_file_get(struct sshfs_file *sf) { sf->refs++; } static int sshfs_release(const char *path, struct fuse_file_info *fi) { struct sshfs_file *sf = get_sshfs_file(fi); struct buffer *handle = &sf->handle; if (sshfs_file_is_conn(sf)) { sshfs_flush(path, fi); sftp_request(SSH_FXP_CLOSE, handle, 0, NULL); } buf_free(handle); chunk_put_locked(sf->readahead); sshfs_file_put(sf); return 0; } static void sshfs_read_end(struct request *req) { struct read_req *rreq = (struct read_req *) req->data; if (req->error) rreq->res = req->error; else if (req->replied) { rreq->res = -EIO; if (req->reply_type == SSH_FXP_STATUS) { uint32_t serr; if (buf_get_uint32(&req->reply, &serr) != -1) { if (serr == SSH_FX_EOF) rreq->res = 0; else rreq->res = -sftp_error_to_errno(serr); } } else if (req->reply_type == SSH_FXP_DATA) { uint32_t retsize; if (buf_get_uint32(&req->reply, &retsize) != -1) { if (retsize > rreq->size) { fprintf(stderr, "long read\n"); } else if (buf_check_get(&req->reply, retsize) != -1) { rreq->res = retsize; rreq->data = req->reply; buf_init(&req->reply, 0); } } } else { fprintf(stderr, "protocol error\n"); } } else { rreq->res = -EIO; } rreq->sio->num_reqs--; if (!rreq->sio->num_reqs) pthread_cond_broadcast(&rreq->sio->finished); } static void sshfs_read_begin(struct request *req) { struct read_req *rreq = (struct read_req *) req->data; rreq->sio->num_reqs++; } static struct read_chunk *sshfs_send_read(struct sshfs_file *sf, size_t size, off_t offset) { struct read_chunk *chunk = g_new0(struct read_chunk, 1); struct buffer *handle = &sf->handle; pthread_cond_init(&chunk->sio.finished, NULL); list_init(&chunk->reqs); chunk->size = size; chunk->offset = offset; chunk->refs = 1; while (size) { int err; struct buffer buf; struct iovec iov[1]; struct read_req *rreq; size_t bsize = size < sshfs.max_read ? size : sshfs.max_read; rreq = g_new0(struct read_req, 1); rreq->sio = &chunk->sio; rreq->size = bsize; buf_init(&rreq->data, 0); list_add(&rreq->list, &chunk->reqs); buf_init(&buf, 0); buf_add_buf(&buf, handle); buf_add_uint64(&buf, offset); buf_add_uint32(&buf, bsize); buf_to_iov(&buf, &iov[0]); err = sftp_request_send(SSH_FXP_READ, iov, 1, sshfs_read_begin, sshfs_read_end, 0, rreq, NULL); buf_free(&buf); if (err) break; size -= bsize; offset += bsize; } return chunk; } static int wait_chunk(struct read_chunk *chunk, char *buf, size_t size) { int res = 0; struct read_req *rreq; pthread_mutex_lock(&sshfs.lock); while (chunk->sio.num_reqs) pthread_cond_wait(&chunk->sio.finished, &sshfs.lock); pthread_mutex_unlock(&sshfs.lock); if (chunk->sio.error) { if (chunk->sio.error != MY_EOF) res = chunk->sio.error; goto out; } while (!list_empty(&chunk->reqs) && size) { rreq = list_entry(chunk->reqs.prev, struct read_req, list); if (rreq->res < 0) { chunk->sio.error = rreq->res; break; } if (rreq->res == 0) { chunk->sio.error = MY_EOF; break; } else if (size < (size_t) rreq->res) { buf_get_mem(&rreq->data, buf, size); rreq->res -= size; rreq->size -= size; res += size; break; } else { buf_get_mem(&rreq->data, buf, rreq->res); res += rreq->res; if ((size_t) rreq->res < rreq->size) { chunk->sio.error = MY_EOF; break; } buf += rreq->res; size -= rreq->res; list_del(&rreq->list); buf_free(&rreq->data); g_free(rreq); } } if (res > 0) { chunk->offset += res; chunk->size -= res; } out: chunk_put_locked(chunk); return res; } static int sshfs_sync_read(struct sshfs_file *sf, char *buf, size_t size, off_t offset) { struct read_chunk *chunk; chunk = sshfs_send_read(sf, size, offset); return wait_chunk(chunk, buf, size); } static void submit_read(struct sshfs_file *sf, size_t size, off_t offset, struct read_chunk **chunkp) { struct read_chunk *chunk; chunk = sshfs_send_read(sf, size, offset); pthread_mutex_lock(&sshfs.lock); chunk->modifver = sshfs.modifver; chunk_put(*chunkp); *chunkp = chunk; chunk->refs++; pthread_mutex_unlock(&sshfs.lock); } static struct read_chunk *search_read_chunk(struct sshfs_file *sf, off_t offset) { struct read_chunk *ch = sf->readahead; if (ch && ch->offset == offset && ch->modifver == sshfs.modifver) { ch->refs++; return ch; } else return NULL; } static int sshfs_async_read(struct sshfs_file *sf, char *rbuf, size_t size, off_t offset) { int res = 0; size_t total = 0; struct read_chunk *chunk; struct read_chunk *chunk_prev = NULL; size_t origsize = size; int curr_is_seq; pthread_mutex_lock(&sshfs.lock); curr_is_seq = sf->is_seq; sf->is_seq = (sf->next_pos == offset && sf->modifver == sshfs.modifver); sf->next_pos = offset + size; sf->modifver = sshfs.modifver; chunk = search_read_chunk(sf, offset); pthread_mutex_unlock(&sshfs.lock); if (chunk && chunk->size < size) { chunk_prev = chunk; size -= chunk->size; offset += chunk->size; chunk = NULL; } if (!chunk) submit_read(sf, size, offset, &chunk); if (curr_is_seq && chunk && chunk->size <= size) submit_read(sf, origsize, offset + size, &sf->readahead); if (chunk_prev) { size_t prev_size = chunk_prev->size; res = wait_chunk(chunk_prev, rbuf, prev_size); if (res < (int) prev_size) { chunk_put_locked(chunk); return res; } rbuf += res; total += res; } res = wait_chunk(chunk, rbuf, size); if (res > 0) total += res; if (res < 0) return res; return total; } static int sshfs_read(const char *path, char *rbuf, size_t size, off_t offset, struct fuse_file_info *fi) { struct sshfs_file *sf = get_sshfs_file(fi); (void) path; if (!sshfs_file_is_conn(sf)) return -EIO; if (sshfs.sync_read) return sshfs_sync_read(sf, rbuf, size, offset); else return sshfs_async_read(sf, rbuf, size, offset); } static void sshfs_write_begin(struct request *req) { struct sshfs_file *sf = (struct sshfs_file *) req->data; sshfs_file_get(sf); list_add(&req->list, &sf->write_reqs); } static void sshfs_write_end(struct request *req) { uint32_t serr; struct sshfs_file *sf = (struct sshfs_file *) req->data; if (req->error) sf->write_error = req->error; else if (req->replied) { if (req->reply_type != SSH_FXP_STATUS) { fprintf(stderr, "protocol error\n"); } else if (buf_get_uint32(&req->reply, &serr) != -1 && serr != SSH_FX_OK) { sf->write_error = -EIO; } } list_del(&req->list); pthread_cond_broadcast(&sf->write_finished); sshfs_file_put(sf); } static int sshfs_async_write(struct sshfs_file *sf, const char *wbuf, size_t size, off_t offset) { int err = 0; struct buffer *handle = &sf->handle; while (!err && size) { struct buffer buf; struct iovec iov[2]; size_t bsize = size < sshfs.max_write ? size : sshfs.max_write; buf_init(&buf, 0); buf_add_buf(&buf, handle); buf_add_uint64(&buf, offset); buf_add_uint32(&buf, bsize); buf_to_iov(&buf, &iov[0]); iov[1].iov_base = (void *) wbuf; iov[1].iov_len = bsize; err = sftp_request_send(SSH_FXP_WRITE, iov, 2, sshfs_write_begin, sshfs_write_end, 0, sf, NULL); buf_free(&buf); size -= bsize; wbuf += bsize; offset += bsize; } return err; } static void sshfs_sync_write_begin(struct request *req) { struct sshfs_io *sio = (struct sshfs_io *) req->data; sio->num_reqs++; } static void sshfs_sync_write_end(struct request *req) { uint32_t serr; struct sshfs_io *sio = (struct sshfs_io *) req->data; if (req->error) { sio->error = req->error; } else if (req->replied) { if (req->reply_type != SSH_FXP_STATUS) { fprintf(stderr, "protocol error\n"); } else if (buf_get_uint32(&req->reply, &serr) != -1 && serr != SSH_FX_OK) { sio->error = -EIO; } } sio->num_reqs--; if (!sio->num_reqs) pthread_cond_broadcast(&sio->finished); } static int sshfs_sync_write(struct sshfs_file *sf, const char *wbuf, size_t size, off_t offset) { int err = 0; struct buffer *handle = &sf->handle; struct sshfs_io sio = { .error = 0, .num_reqs = 0 }; pthread_cond_init(&sio.finished, NULL); while (!err && size) { struct buffer buf; struct iovec iov[2]; size_t bsize = size < sshfs.max_write ? size : sshfs.max_write; buf_init(&buf, 0); buf_add_buf(&buf, handle); buf_add_uint64(&buf, offset); buf_add_uint32(&buf, bsize); buf_to_iov(&buf, &iov[0]); iov[1].iov_base = (void *) wbuf; iov[1].iov_len = bsize; err = sftp_request_send(SSH_FXP_WRITE, iov, 2, sshfs_sync_write_begin, sshfs_sync_write_end, 0, &sio, NULL); buf_free(&buf); size -= bsize; wbuf += bsize; offset += bsize; } pthread_mutex_lock(&sshfs.lock); while (sio.num_reqs) pthread_cond_wait(&sio.finished, &sshfs.lock); pthread_mutex_unlock(&sshfs.lock); if (!err) err = sio.error; return err; } static int sshfs_write(const char *path, const char *wbuf, size_t size, off_t offset, struct fuse_file_info *fi) { int err; struct sshfs_file *sf = get_sshfs_file(fi); (void) path; if (!sshfs_file_is_conn(sf)) return -EIO; sshfs_inc_modifver(); if (!sshfs.sync_write && !sf->write_error) err = sshfs_async_write(sf, wbuf, size, offset); else err = sshfs_sync_write(sf, wbuf, size, offset); return err ? err : (int) size; } static int sshfs_ext_statvfs(const char *path, struct statvfs *stbuf) { int err; struct buffer buf; struct buffer outbuf; buf_init(&buf, 0); buf_add_string(&buf, SFTP_EXT_STATVFS); buf_add_path(&buf, path); err = sftp_request(SSH_FXP_EXTENDED, &buf, SSH_FXP_EXTENDED_REPLY, &outbuf); if (!err) { if (buf_get_statvfs(&outbuf, stbuf) == -1) err = -EIO; buf_free(&outbuf); } buf_free(&buf); return err; } #if FUSE_VERSION >= 25 static int sshfs_statfs(const char *path, struct statvfs *buf) { if (sshfs.ext_statvfs) return sshfs_ext_statvfs(path, buf); buf->f_namemax = 255; buf->f_bsize = sshfs.blksize; /* * df seems to use f_bsize instead of f_frsize, so make them * the same */ buf->f_frsize = buf->f_bsize; buf->f_blocks = buf->f_bfree = buf->f_bavail = 1000ULL * 1024 * 1024 * 1024 / buf->f_frsize; buf->f_files = buf->f_ffree = 1000000000; return 0; } #else static int sshfs_statfs(const char *path, struct statfs *buf) { if (sshfs.ext_statvfs) { int err; struct statvfs vbuf; err = sshfs_ext_statvfs(path, &vbuf); if (!err) { buf->f_bsize = vbuf.f_bsize; buf->f_blocks = vbuf.f_blocks; buf->f_bfree = vbuf.f_bfree; buf->f_bavail = vbuf.f_bavail; buf->f_files = vbuf.f_files; buf->f_ffree = vbuf.f_ffree; buf->f_namelen = vbuf.f_namemax; } return err; } buf->f_namelen = 255; buf->f_bsize = sshfs.blksize; buf->f_blocks = buf->f_bfree = buf->f_bavail = 1000ULL * 1024 * 1024 * 1024 / buf->f_bsize; buf->f_files = buf->f_ffree = 1000000000; return 0; } #endif #if FUSE_VERSION >= 25 static int sshfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) { return sshfs_open_common(path, mode, fi); } static int sshfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) { int err; struct buffer buf; struct sshfs_file *sf = get_sshfs_file(fi); (void) path; if (!sshfs_file_is_conn(sf)) return -EIO; sshfs_inc_modifver(); if (sshfs.truncate_workaround) return sshfs_truncate_workaround(path, size, fi); buf_init(&buf, 0); buf_add_buf(&buf, &sf->handle); buf_add_uint32(&buf, SSH_FILEXFER_ATTR_SIZE); buf_add_uint64(&buf, size); err = sftp_request(SSH_FXP_FSETSTAT, &buf, SSH_FXP_STATUS, NULL); buf_free(&buf); return err; } #endif static int sshfs_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) { int err; struct buffer buf; struct buffer outbuf; struct sshfs_file *sf = get_sshfs_file(fi); (void) path; if (!sshfs_file_is_conn(sf)) return -EIO; if (sshfs.fstat_workaround) return sshfs_getattr(path, stbuf); buf_init(&buf, 0); buf_add_buf(&buf, &sf->handle); err = sftp_request(SSH_FXP_FSTAT, &buf, SSH_FXP_ATTRS, &outbuf); if (!err) { err = buf_get_attrs(&outbuf, stbuf, NULL); buf_free(&outbuf); } buf_free(&buf); return err; } static int sshfs_truncate_zero(const char *path) { int err; struct fuse_file_info fi; fi.flags = O_WRONLY | O_TRUNC; err = sshfs_open(path, &fi); if (!err) sshfs_release(path, &fi); return err; } static size_t calc_buf_size(off_t size, off_t offset) { return offset + sshfs.max_read < size ? sshfs.max_read : size - offset; } static int sshfs_truncate_shrink(const char *path, off_t size) { int res; char *data; off_t offset; struct fuse_file_info fi; data = calloc(size, 1); if (!data) return -ENOMEM; fi.flags = O_RDONLY; res = sshfs_open(path, &fi); if (res) goto out; for (offset = 0; offset < size; offset += res) { size_t bufsize = calc_buf_size(size, offset); res = sshfs_read(path, data + offset, bufsize, offset, &fi); if (res <= 0) break; } sshfs_release(path, &fi); if (res < 0) goto out; fi.flags = O_WRONLY | O_TRUNC; res = sshfs_open(path, &fi); if (res) goto out; for (offset = 0; offset < size; offset += res) { size_t bufsize = calc_buf_size(size, offset); res = sshfs_write(path, data + offset, bufsize, offset, &fi); if (res < 0) break; } if (res >= 0) res = sshfs_flush(path, &fi); sshfs_release(path, &fi); out: free(data); return res; } static int sshfs_truncate_extend(const char *path, off_t size, struct fuse_file_info *fi) { int res; char c = 0; struct fuse_file_info tmpfi; struct fuse_file_info *openfi = fi; if (!fi) { openfi = &tmpfi; openfi->flags = O_WRONLY; res = sshfs_open(path, openfi); if (res) return res; } res = sshfs_write(path, &c, 1, size - 1, openfi); if (res == 1) res = sshfs_flush(path, openfi); if (!fi) sshfs_release(path, openfi); return res; } /* * Work around broken sftp servers which don't handle * SSH_FILEXFER_ATTR_SIZE in SETSTAT request. * * If new size is zero, just open the file with O_TRUNC. * * If new size is smaller than current size, then copy file locally, * then open/trunc and send it back. * * If new size is greater than current size, then write a zero byte to * the new end of the file. */ static int sshfs_truncate_workaround(const char *path, off_t size, struct fuse_file_info *fi) { if (size == 0) return sshfs_truncate_zero(path); else { struct stat stbuf; int err; if (fi) err = sshfs_fgetattr(path, &stbuf, fi); else err = sshfs_getattr(path, &stbuf); if (err) return err; if (stbuf.st_size == size) return 0; else if (stbuf.st_size > size) return sshfs_truncate_shrink(path, size); else return sshfs_truncate_extend(path, size, fi); } } static int processing_init(void) { signal(SIGPIPE, SIG_IGN); pthread_mutex_init(&sshfs.lock, NULL); pthread_mutex_init(&sshfs.lock_write, NULL); pthread_cond_init(&sshfs.outstanding_cond, NULL); sshfs.reqtab = g_hash_table_new(NULL, NULL); if (!sshfs.reqtab) { fprintf(stderr, "failed to create hash table\n"); return -1; } return 0; } static struct fuse_cache_operations sshfs_oper = { .oper = { .init = sshfs_init, .getattr = sshfs_getattr, .access = sshfs_access, .readlink = sshfs_readlink, .mknod = sshfs_mknod, .mkdir = sshfs_mkdir, .symlink = sshfs_symlink, .unlink = sshfs_unlink, .rmdir = sshfs_rmdir, .rename = sshfs_rename, .link = sshfs_link, .chmod = sshfs_chmod, .chown = sshfs_chown, .truncate = sshfs_truncate, .utime = sshfs_utime, .open = sshfs_open, .flush = sshfs_flush, .fsync = sshfs_fsync, .release = sshfs_release, .read = sshfs_read, .write = sshfs_write, .statfs = sshfs_statfs, #if FUSE_VERSION >= 25 .create = sshfs_create, .ftruncate = sshfs_ftruncate, .fgetattr = sshfs_fgetattr, #endif #if FUSE_VERSION >= 29 .flag_nullpath_ok = 1, .flag_nopath = 1, #endif }, .cache_getdir = sshfs_getdir, }; static void usage(const char *progname) { printf( "usage: %s [user@]host:[dir] mountpoint [options]\n" "\n" "general options:\n" " -o opt,[opt...] mount options\n" " -h --help print help\n" " -V --version print version\n" "\n" "SSHFS options:\n" " -p PORT equivalent to '-o port=PORT'\n" " -C equivalent to '-o compression=yes'\n" " -F ssh_configfile specifies alternative ssh configuration file\n" " -1 equivalent to '-o ssh_protocol=1'\n" " -o reconnect reconnect to server\n" " -o delay_connect delay connection to server\n" " -o sshfs_sync synchronous writes\n" " -o no_readahead synchronous reads (no speculative readahead)\n" " -o sync_readdir synchronous readdir\n" " -o sshfs_debug print some debugging information\n" " -o cache=BOOL enable caching {yes,no} (default: yes)\n" " -o cache_max_size=N sets the maximum size of the cache (default: 10000)\n" " -o cache_timeout=N sets timeout for caches in seconds (default: 20)\n" " -o cache_X_timeout=N sets timeout for {stat,dir,link} cache\n" " -o cache_clean_interval=N\n" " sets the interval for automatic cleaning of the\n" " cache (default: 60)\n" " -o cache_min_clean_interval=N\n" " sets the interval for forced cleaning of the\n" " cache if full (default: 5)\n" " -o workaround=LIST colon separated list of workarounds\n" " none no workarounds enabled\n" " all all workarounds enabled\n" " [no]rename fix renaming to existing file (default: off)\n" #ifdef SSH_NODELAY_WORKAROUND " [no]nodelay set nodelay tcp flag in ssh (default: on)\n" #endif " [no]nodelaysrv set nodelay tcp flag in sshd (default: off)\n" " [no]truncate fix truncate for old servers (default: off)\n" " [no]buflimit fix buffer fillup bug in server (default: on)\n" " -o idmap=TYPE user/group ID mapping (default: " IDMAP_DEFAULT ")\n" " none no translation of the ID space\n" " user only translate UID/GID of connecting user\n" " file translate UIDs/GIDs contained in uidfile/gidfile\n" " -o uidfile=FILE file containing username:remote_uid mappings\n" " -o gidfile=FILE file containing groupname:remote_gid mappings\n" " -o nomap=TYPE with idmap=file, how to handle missing mappings\n" " ignore don't do any re-mapping\n" " error return an error (default)\n" " -o ssh_command=CMD execute CMD instead of 'ssh'\n" " -o ssh_protocol=N ssh protocol to use (default: 2)\n" " -o sftp_server=SERV path to sftp server or subsystem (default: sftp)\n" " -o directport=PORT directly connect to PORT bypassing ssh\n" " -o slave communicate over stdin and stdout bypassing network\n" " -o disable_hardlink link(2) will return with errno set to ENOSYS\n" " -o transform_symlinks transform absolute symlinks to relative\n" " -o follow_symlinks follow symlinks on the server\n" " -o no_check_root don't check for existence of 'dir' on server\n" " -o password_stdin read password from stdin (only for pam_mount!)\n" " -o SSHOPT=VAL ssh options (see man ssh_config)\n" "\n", progname); } static int is_ssh_opt(const char *arg) { if (arg[0] != '-') { unsigned arglen = strlen(arg); const char **o; for (o = ssh_opts; *o; o++) { unsigned olen = strlen(*o); if (arglen > olen && arg[olen] == '=' && strncasecmp(arg, *o, olen) == 0) return 1; } } return 0; } static int sshfs_fuse_main(struct fuse_args *args) { sshfs.op = cache_init(&sshfs_oper); #if FUSE_VERSION >= 26 return fuse_main(args->argc, args->argv, sshfs.op, NULL); #else return fuse_main(args->argc, args->argv, sshfs.op); #endif } static int sshfs_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) { char *tmp; (void) data; switch (key) { case FUSE_OPT_KEY_OPT: if (is_ssh_opt(arg)) { tmp = g_strdup_printf("-o%s", arg); ssh_add_arg(tmp); g_free(tmp); return 0; } return 1; case FUSE_OPT_KEY_NONOPT: if (!sshfs.host && strchr(arg, ':')) { sshfs.host = strdup(arg); return 0; } return 1; case KEY_PORT: tmp = g_strdup_printf("-oPort=%s", arg + 2); ssh_add_arg(tmp); g_free(tmp); return 0; case KEY_COMPRESS: ssh_add_arg("-oCompression=yes"); return 0; case KEY_CONFIGFILE: tmp = g_strdup_printf("-F%s", arg + 2); ssh_add_arg(tmp); g_free(tmp); return 0; case KEY_HELP: usage(outargs->argv[0]); fuse_opt_add_arg(outargs, "-ho"); sshfs_fuse_main(outargs); exit(1); case KEY_VERSION: printf("SSHFS version %s\n", PACKAGE_VERSION); #if FUSE_VERSION >= 25 fuse_opt_add_arg(outargs, "--version"); sshfs_fuse_main(outargs); #endif exit(0); case KEY_FOREGROUND: sshfs.foreground = 1; return 1; default: fprintf(stderr, "internal error\n"); abort(); } } static int workaround_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) { (void) data; (void) key; (void) outargs; fprintf(stderr, "unknown workaround: '%s'\n", arg); return -1; } int parse_workarounds(void) { int res; char *argv[] = { "", "-o", sshfs.workarounds, NULL }; struct fuse_args args = FUSE_ARGS_INIT(3, argv); char *s = sshfs.workarounds; if (!s) return 0; while ((s = strchr(s, ':'))) *s = ','; res = fuse_opt_parse(&args, &sshfs, workaround_opts, workaround_opt_proc); fuse_opt_free_args(&args); return res; } #if FUSE_VERSION == 25 static 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; } #endif static void check_large_read(struct fuse_args *args) { struct utsname buf; int err = uname(&buf); if (!err && strcmp(buf.sysname, "Linux") == 0 && strncmp(buf.release, "2.4.", 4) == 0) fuse_opt_insert_arg(args, 1, "-olarge_read"); } static int read_password(void) { int size = getpagesize(); int max_password = MIN(MAX_PASSWORD, size - 1); int n; sshfs.password = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, -1, 0); if (sshfs.password == MAP_FAILED) { perror("Failed to allocate locked page for password"); return -1; } if (mlock(sshfs.password, size) != 0) { memset(sshfs.password, 0, size); munmap(sshfs.password, size); sshfs.password = NULL; perror("Failed to allocate locked page for password"); return -1; } /* Don't use fgets() because password might stay in memory */ for (n = 0; n < max_password; n++) { int res; res = read(0, &sshfs.password[n], 1); if (res == -1) { perror("Reading password"); return -1; } if (res == 0) { sshfs.password[n] = '\n'; break; } if (sshfs.password[n] == '\n') break; } if (n == max_password) { fprintf(stderr, "Password too long\n"); return -1; } sshfs.password[n+1] = '\0'; ssh_add_arg("-oNumberOfPasswordPrompts=1"); return 0; } static void set_ssh_command(void) { char *s; char *d; int i = 0; int end = 0; d = sshfs.ssh_command; s = sshfs.ssh_command; while (!end) { switch (*s) { case '\0': end = 1; case ' ': *d = '\0'; if (i == 0) { replace_arg(&sshfs.ssh_args.argv[0], sshfs.ssh_command); } else { if (fuse_opt_insert_arg(&sshfs.ssh_args, i, sshfs.ssh_command) == -1) _exit(1); } i++; d = sshfs.ssh_command; break; case '\\': if (s[1]) s++; default: *d++ = *s; } s++; } } static char *find_base_path(void) { char *s = sshfs.host; char *d = s; for (; *s && *s != ':'; s++) { if (*s == '[') { /* * Handle IPv6 numerical address enclosed in square * brackets */ s++; for (; *s != ']'; s++) { if (!*s) { fprintf(stderr, "missing ']' in hostname\n"); exit(1); } *d++ = *s; } } else { *d++ = *s; } } *d++ = '\0'; s++; return s; } /* * Remove commas from fsname, as it confuses the fuse option parser. */ static void fsname_remove_commas(char *fsname) { if (strchr(fsname, ',') != NULL) { char *s = fsname; char *d = s; for (; *s; s++) { if (*s != ',') *d++ = *s; } *d = *s; } } #if FUSE_VERSION >= 27 static char *fsname_escape_commas(char *fsnameold) { char *fsname = g_malloc(strlen(fsnameold) * 2 + 1); char *d = fsname; char *s; for (s = fsnameold; *s; s++) { if (*s == '\\' || *s == ',') *d++ = '\\'; *d++ = *s; } *d = '\0'; g_free(fsnameold); return fsname; } #endif static int ssh_connect(void) { int res; res = processing_init(); if (res == -1) return -1; if (!sshfs.delay_connect) { if (connect_remote() == -1) return -1; if (!sshfs.no_check_root && sftp_check_root(sshfs.base_path) != 0) return -1; } return 0; } /* number of ':' separated fields in a passwd/group file that we care * about */ #define IDMAP_FIELDS 3 /* given a line from a uidmap or gidmap, parse out the name and id */ static void parse_idmap_line(char *line, const char* filename, const unsigned int lineno, uint32_t *ret_id, char **ret_name, const int eof) { /* chomp off the trailing newline */ char *p = line; if ((p = strrchr(line, '\n'))) *p = '\0'; else if (!eof) { fprintf(stderr, "%s:%u: line too long\n", filename, lineno); exit(1); } char *tokens[IDMAP_FIELDS]; char *tok; int i; for (i = 0; (tok = strsep(&line, ":")) && (i < IDMAP_FIELDS) ; i++) { tokens[i] = tok; } char *name_tok, *id_tok; if (i == 2) { /* assume name:id format */ name_tok = tokens[0]; id_tok = tokens[1]; } else if (i >= IDMAP_FIELDS) { /* assume passwd/group file format */ name_tok = tokens[0]; id_tok = tokens[2]; } else { fprintf(stderr, "%s:%u: unknown format\n", filename, lineno); exit(1); } errno = 0; uint32_t remote_id = strtoul(id_tok, NULL, 10); if (errno) { fprintf(stderr, "Invalid id number on line %u of '%s': %s\n", lineno, filename, strerror(errno)); exit(1); } *ret_name = strdup(name_tok); *ret_id = remote_id; } /* read a uidmap or gidmap */ static void read_id_map(char *file, uint32_t *(*map_fn)(char *), const char *name_id, GHashTable **idmap, GHashTable **r_idmap) { *idmap = g_hash_table_new(NULL, NULL); *r_idmap = g_hash_table_new(NULL, NULL); FILE *fp; char line[LINE_MAX]; unsigned int lineno = 0; uid_t local_uid = getuid(); fp = fopen(file, "r"); if (fp == NULL) { fprintf(stderr, "failed to open '%s': %s\n", file, strerror(errno)); exit(1); } struct stat st; if (fstat(fileno(fp), &st) == -1) { fprintf(stderr, "failed to stat '%s': %s\n", file, strerror(errno)); exit(1); } if (st.st_uid != local_uid) { fprintf(stderr, "'%s' is not owned by uid %lu\n", file, (unsigned long)local_uid); exit(1); } if (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) { fprintf(stderr, "'%s' is writable by other users\n", file); exit(1); } while (fgets(line, LINE_MAX, fp) != NULL) { lineno++; uint32_t remote_id; char *name; /* skip blank lines */ if (line[0] == '\n' || line[0] == '\0') continue; parse_idmap_line(line, file, lineno, &remote_id, &name, feof(fp)); uint32_t *local_id = map_fn(name); if (local_id == NULL) { /* not found */ DEBUG("%s(%u): no local %s\n", name, remote_id, name_id); free(name); continue; } DEBUG("%s: remote %s %u => local %s %u\n", name, name_id, remote_id, name_id, *local_id); g_hash_table_insert(*idmap, GUINT_TO_POINTER(remote_id), GUINT_TO_POINTER(*local_id)); g_hash_table_insert(*r_idmap, GUINT_TO_POINTER(*local_id), GUINT_TO_POINTER(remote_id)); free(name); free(local_id); } if (fclose(fp) == EOF) { fprintf(stderr, "failed to close '%s': %s", file, strerror(errno)); exit(1); } } /* given a username, return a pointer to its uid, or NULL if it doesn't * exist on this system */ static uint32_t *username_to_uid(char *name) { errno = 0; struct passwd *pw = getpwnam(name); if (pw == NULL) { if (errno == 0) { /* "does not exist" */ return NULL; } fprintf(stderr, "Failed to look up user '%s': %s\n", name, strerror(errno)); exit(1); } uint32_t *r = malloc(sizeof(uint32_t)); if (r == NULL) { fprintf(stderr, "sshfs: memory allocation failed\n"); abort(); } *r = pw->pw_uid; return r; } /* given a groupname, return a pointer to its gid, or NULL if it doesn't * exist on this system */ static uint32_t *groupname_to_gid(char *name) { errno = 0; struct group *gr = getgrnam(name); if (gr == NULL) { if (errno == 0) { /* "does not exist" */ return NULL; } fprintf(stderr, "Failed to look up group '%s': %s\n", name, strerror(errno)); exit(1); } uint32_t *r = malloc(sizeof(uint32_t)); if (r == NULL) { fprintf(stderr, "sshfs: memory allocation failed\n"); abort(); } *r = gr->gr_gid; return r; } static inline void load_uid_map(void) { read_id_map(sshfs.uid_file, &username_to_uid, "uid", &sshfs.uid_map, &sshfs.r_uid_map); } static inline void load_gid_map(void) { read_id_map(sshfs.gid_file, &groupname_to_gid, "gid", &sshfs.gid_map, &sshfs.r_gid_map); } #ifdef __APPLE__ int main(int argc, char *argv[], __unused char *envp[], char **exec_path) #else int main(int argc, char *argv[]) #endif { int res; struct fuse_args args = FUSE_ARGS_INIT(argc, argv); char *tmp; char *fsname; const char *sftp_server; int libver; #ifdef __APPLE__ if (!realpath(*exec_path, sshfs_program_path)) { memset(sshfs_program_path, 0, PATH_MAX); } /* Until this gets fixed somewhere else. */ g_slice_set_config(G_SLICE_CONFIG_ALWAYS_MALLOC, TRUE); #endif /* __APPLE__ */ g_thread_init(NULL); sshfs.blksize = 4096; /* SFTP spec says all servers should allow at least 32k I/O */ sshfs.max_read = 32768; sshfs.max_write = 32768; sshfs.nodelay_workaround = 1; sshfs.nodelaysrv_workaround = 0; #ifdef __APPLE__ sshfs.rename_workaround = 1; #else sshfs.rename_workaround = 0; #endif sshfs.truncate_workaround = 0; sshfs.buflimit_workaround = 1; sshfs.ssh_ver = 2; sshfs.progname = argv[0]; sshfs.rfd = -1; sshfs.wfd = -1; sshfs.ptyfd = -1; sshfs.ptyslavefd = -1; sshfs.delay_connect = 0; sshfs.slave = 0; sshfs.detect_uid = 0; if (strcmp(IDMAP_DEFAULT, "none") == 0) { sshfs.idmap = IDMAP_NONE; } else if (strcmp(IDMAP_DEFAULT, "user") == 0) { sshfs.idmap = IDMAP_USER; } else { fprintf(stderr, "bad idmap default value built into sshfs; " "assuming none (bad logic in configure script?)\n"); sshfs.idmap = IDMAP_NONE; } sshfs.nomap = NOMAP_ERROR; ssh_add_arg("ssh"); ssh_add_arg("-x"); ssh_add_arg("-a"); ssh_add_arg("-oClearAllForwardings=yes"); if (fuse_opt_parse(&args, &sshfs, sshfs_opts, sshfs_opt_proc) == -1 || parse_workarounds() == -1) exit(1); if (sshfs.idmap == IDMAP_USER) sshfs.detect_uid = 1; else if (sshfs.idmap == IDMAP_FILE) { sshfs.uid_map = NULL; sshfs.gid_map = NULL; sshfs.r_uid_map = NULL; sshfs.r_gid_map = NULL; if (!sshfs.uid_file && !sshfs.gid_file) { fprintf(stderr, "need a uidfile or gidfile with idmap=file\n"); exit(1); } if (sshfs.uid_file) load_uid_map(); if (sshfs.gid_file) load_gid_map(); } free(sshfs.uid_file); free(sshfs.gid_file); DEBUG("SSHFS version %s\n", PACKAGE_VERSION); if (sshfs.slave) { /* Force sshfs to the foreground when using stdin+stdout */ sshfs.foreground = 1; } if (sshfs.slave && sshfs.password_stdin) { fprintf(stderr, "the password_stdin and slave options cannot both be specified\n"); exit(1); } if (sshfs.password_stdin) { res = read_password(); if (res == -1) exit(1); } if (sshfs.buflimit_workaround) /* Work around buggy sftp-server in OpenSSH. Without this on a slow server a 10Mbyte buffer would fill up and the server would abort */ sshfs.max_outstanding_len = 8388608; else sshfs.max_outstanding_len = ~0; if (!sshfs.host) { fprintf(stderr, "missing host\n"); fprintf(stderr, "see `%s -h' for usage\n", argv[0]); exit(1); } fsname = g_strdup(sshfs.host); sshfs.base_path = g_strdup(find_base_path()); if (sshfs.ssh_command) set_ssh_command(); tmp = g_strdup_printf("-%i", sshfs.ssh_ver); ssh_add_arg(tmp); g_free(tmp); ssh_add_arg(sshfs.host); if (sshfs.sftp_server) sftp_server = sshfs.sftp_server; else if (sshfs.ssh_ver == 1) sftp_server = SFTP_SERVER_PATH; else sftp_server = "sftp"; if (sshfs.ssh_ver != 1 && strchr(sftp_server, '/') == NULL) ssh_add_arg("-s"); ssh_add_arg(sftp_server); free(sshfs.sftp_server); res = cache_parse_options(&args); if (res == -1) exit(1); sshfs.randseed = time(0); if (sshfs.max_read > 65536) sshfs.max_read = 65536; if (sshfs.max_write > 65536) sshfs.max_write = 65536; if (fuse_is_lib_option("ac_attr_timeout=")) fuse_opt_insert_arg(&args, 1, "-oauto_cache,ac_attr_timeout=0"); #if FUSE_VERSION >= 27 libver = fuse_version(); assert(libver >= 27); if (libver >= 28) fsname = fsname_escape_commas(fsname); else fsname_remove_commas(fsname); tmp = g_strdup_printf("-osubtype=sshfs,fsname=%s", fsname); #else fsname_remove_commas(fsname); tmp = g_strdup_printf("-ofsname=sshfs#%s", fsname); #endif fuse_opt_insert_arg(&args, 1, tmp); g_free(tmp); g_free(fsname); check_large_read(&args); #if FUSE_VERSION >= 26 { struct fuse *fuse; struct fuse_chan *ch; char *mountpoint; int multithreaded; int foreground; struct stat st; res = fuse_parse_cmdline(&args, &mountpoint, &multithreaded, &foreground); if (res == -1) exit(1); if (sshfs.slave) { /* Force sshfs to the foreground when using stdin+stdout */ foreground = 1; } res = stat(mountpoint, &st); if (res == -1) { perror(mountpoint); exit(1); } sshfs.mnt_mode = st.st_mode; ch = fuse_mount(mountpoint, &args); if (!ch) exit(1); res = fcntl(fuse_chan_fd(ch), F_SETFD, FD_CLOEXEC); if (res == -1) perror("WARNING: failed to set FD_CLOEXEC on fuse device"); sshfs.op = cache_init(&sshfs_oper); fuse = fuse_new(ch, &args, sshfs.op, sizeof(struct fuse_operations), NULL); if (fuse == NULL) { fuse_unmount(mountpoint, ch); exit(1); } /* * FIXME: trim $PATH so it doesn't contain anything inside the * mountpoint, which would deadlock. */ res = ssh_connect(); if (res == -1) { fuse_unmount(mountpoint, ch); fuse_destroy(fuse); exit(1); } res = fuse_daemonize(foreground); if (res != -1) res = fuse_set_signal_handlers(fuse_get_session(fuse)); if (res == -1) { fuse_unmount(mountpoint, ch); fuse_destroy(fuse); exit(1); } if (multithreaded) res = fuse_loop_mt(fuse); else res = fuse_loop(fuse); if (res == -1) res = 1; else res = 0; fuse_remove_signal_handlers(fuse_get_session(fuse)); fuse_unmount(mountpoint, ch); fuse_destroy(fuse); free(mountpoint); } #else res = ssh_connect(); if (res == -1) exit(1); res = sshfs_fuse_main(&args); #endif if (sshfs.debug) { unsigned int avg_rtt = 0; if (sshfs.num_sent) avg_rtt = sshfs.total_rtt / sshfs.num_sent; DEBUG("\n" "sent: %llu messages, %llu bytes\n" "received: %llu messages, %llu bytes\n" "rtt min/max/avg: %ums/%ums/%ums\n" "num connect: %u\n", (unsigned long long) sshfs.num_sent, (unsigned long long) sshfs.bytes_sent, (unsigned long long) sshfs.num_received, (unsigned long long) sshfs.bytes_received, sshfs.min_rtt, sshfs.max_rtt, avg_rtt, sshfs.num_connect); } fuse_opt_free_args(&args); fuse_opt_free_args(&sshfs.ssh_args); free(sshfs.directport); return res; } sshfs-sshfs_2.8/sshnodelay.c000066400000000000000000000034041273255547500162540ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include #include /* Wrapper around connect(2) to explicitly set TCP_NODELAY. */ static int nodelay_connect( int (*real_connect)(int, const struct sockaddr *, socklen_t), int sock, const struct sockaddr *addr, socklen_t addrlen) { int res = real_connect(sock, addr, addrlen); if (!res && addr->sa_family == AF_INET) { int opt = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); } return res; } #if __APPLE__ /* OS X does not have LD_PRELOAD but has DYLD_INSERT_LIBRARIES. The right * environment variable is set by sshfs.c when attempting to load the * sshnodelay workaround. * * However, things are not that simple: DYLD_INSERT_LIBRARIES does not * behave exactly like LD_PRELOAD. Instead, the dyld dynamic linker will * look for __DATA __interpose sections on the libraries given via the * DYLD_INSERT_LIBRARIES variable. The contents of this section are pairs * of replacement functions and functions to be replaced, respectively. * Prepare such section here. */ int custom_connect(int sock, const struct sockaddr *addr, socklen_t addrlen); typedef struct interpose_s { void *new_func; void *orig_func; } interpose_t; static const interpose_t interposers[] \ __attribute__ ((section("__DATA, __interpose"))) = { { (void *)custom_connect, (void *)connect }, }; int custom_connect(int sock, const struct sockaddr *addr, socklen_t addrlen) { return nodelay_connect(connect, sock, addr, addrlen); } #else /* !__APPLE__ */ int connect(int sock, const struct sockaddr *addr, socklen_t addrlen) { return nodelay_connect(dlsym(RTLD_NEXT, "connect"), sock, addr, addrlen); } #endif /* !__APPLE__ */