├── .gitignore ├── AUTHORS ├── COPYING ├── ChangeLog ├── Makefile.am ├── NEWS ├── README ├── build.rb ├── cache.c ├── cache.h ├── compat ├── darwin_semaphore.c ├── darwin_semaphore.h ├── fuse_opt.c └── fuse_opt.h ├── configure.ac ├── generate-faq.sh ├── sshfs.1 ├── sshfs.c ├── sshfs.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── sshnodelay.c /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE! Don't add files that are generated in specific 3 | # subdirectories here. Add them in the ".gitignore" file 4 | # in that subdirectory instead. 5 | # 6 | # NOTE! Please use 'git ls-files -i --exclude-standard' 7 | # command after changing this file, to see if there are 8 | # any tracked files which get ignored after the change. 9 | .* 10 | !.gitignore 11 | *.o 12 | *.lo 13 | *.la 14 | *.gz 15 | \#*# 16 | *.orig 17 | *~ 18 | Makefile.in 19 | Makefile 20 | *.m4 21 | stamp-h* 22 | config.* 23 | /sshfs 24 | /ltmain.sh 25 | /configure 26 | /install-sh 27 | /mkinstalldirs 28 | /missing 29 | /*.cache 30 | /depcomp 31 | /compile 32 | /libtool 33 | /INSTALL 34 | /.pc 35 | /patches 36 | /m4 37 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Miklos Szeredi 2 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2012-03-08 Miklos Szeredi 2 | 3 | * Released 2.4 4 | 5 | 2012-03-08 Miklos Szeredi 6 | 7 | * Make sure idmap files aren't writable by others otherwise, other 8 | local users could change the mapping, and gain access to things 9 | they shouldn't. Patch by Mike Kelly 10 | 11 | 2012-02-08 Chris Wolfe 12 | 13 | * Add -o slave. This option routes the sftp communication over stdin 14 | and stdout, bypassing SSH and network. 15 | 16 | 2011-12-16 Mike Kelly 17 | 18 | * Add -o idmap=file, -o uidmap=FILE, -o gidmap=FILE. These options 19 | allow you to create a pair of local files, similar to /etc/passwd or 20 | /etc/group files from the remote server, and use those to remap all 21 | the given UIDs/GIDs. 22 | 23 | 2011-11-25 Miklos Szeredi 24 | 25 | * Make chown respect the UID mapping policy. Reported and tested 26 | by Vivenzio Pagliari 27 | 28 | 2011-11-16 Miklos Szeredi 29 | 30 | * Submit max 32k reads and writes to the sftp server. Also don't 31 | limit the kernel to 64k reads and writes, rather split into 32k 32 | sized chunks and send them to the server all at once. This is 33 | more efficient and less demanding from the server. Reported by 34 | Ludovic Courtès. Fix suggested by Niels Möller 35 | 36 | 2011-11-14 Miklos Szeredi 37 | 38 | * Fix double free if reconnection races with request sending. 39 | Patch by E. Kuemmerle 40 | 41 | * Add locking around modifver and connver 42 | 43 | 2011-10-21 Miklos Szeredi 44 | 45 | * Remove "-oPreferredAuthentications" from ssh options if the 46 | "password_stdin" option is used. Reported by E. Kuemmerle 47 | 48 | 2011-08-24 Miklos Szeredi 49 | 50 | * Add "-oworkaround=fstat" for SFTP servers which don't support 51 | the FSTAT message. Patch by: Percy Jahn 52 | 53 | 2011-07-01 Miklos Szeredi 54 | 55 | * Released 2.3 56 | 57 | 2011-07-01 Miklos Szeredi 58 | 59 | * Add hard link operation. Works if the server supports the 60 | "hardlink@openssh.com" protocol extension. 61 | 62 | 2011-03-25 Miklos Szeredi 63 | 64 | * Fix possible deadlock on reconnection. Reported by Florian 65 | Zumbiehl 66 | 67 | 2011-01-25 Miklos Szeredi 68 | 69 | * Fix cleanup when ssh connection is terminated. This prevents 70 | sshfs hanging when the server is rebooted, for example. 71 | 72 | 2010-03-16 Miklos Szeredi 73 | 74 | * Set FD_CLOEXEC on fuse device. This prevents deadlocks that 75 | happen in some circumstances (bugzilla.kernel.org #12864). 76 | Reported by Tim Connors 77 | 78 | 2009-07-15 Miklos Szeredi 79 | 80 | * Check mountpoint and fuse options before starting an ssh 81 | session (debian bug #535333). This is only supported if compiled 82 | against libfuse-2.6 or later. Reported by Greg Kochanski 83 | 84 | * Check that the remote path specified refers to the same type of 85 | file as the mountpoint, i.e. both are directories or both are 86 | non-directories (debian bug #535343). Reported by Greg Kochanski 87 | 88 | * Allow mounting a single non-directory from the server 89 | 90 | 2009-07-15 Sebastian Dransfeld 91 | 92 | * Add option 'delay_connect': This will always create the sshfs mount, 93 | even if the connection to the server can't be established. 94 | 95 | 2009-01-27 Miklos Szeredi 96 | 97 | * Print usage information and version to stdout, not stderr. 98 | Libfuse also needs to be fixed up. Reported by Kārlis Repsons 99 | 100 | 2008-10-20 Miklos Szeredi 101 | 102 | * Released 2.2 103 | 104 | 2008-10-20 Miklos Szeredi 105 | 106 | * Escape commas in fsname option if libfuse supports it 107 | 108 | 2008-10-08 Miklos Szeredi 109 | 110 | * Handle numerical IPv6 addresses enclosed in square brackets. 111 | Reported by Andre-John Mas 112 | 113 | * Fix error if username contains a comma character. Reported by 114 | Yang Zhang 115 | 116 | * Fix harmless glib assertations with "cache=no" 117 | 118 | 2008-07-11 Miklos Szeredi 119 | 120 | * Released 2.1 121 | 122 | 2008-07-11 Miklos Szeredi 123 | 124 | * Fix statvfs extension to match the current protocol in 125 | opensshfs 126 | 127 | * Check version numbers of extensions, so such changes wouldn't 128 | cause stupid behavior 129 | 130 | 2008-06-24 Miklos Szeredi 131 | 132 | * Add '-F' option to specify the ssh config file. Patch by Pat 133 | Pascal. 134 | 135 | 2008-05-06 Miklos Szeredi 136 | 137 | * Fix bug in caching which could cause file corruption for append 138 | mode writes. Reported by Jose Alonso 139 | 140 | 2008-05-05 Miklos Szeredi 141 | 142 | * Fix compile on OS X. Original patch from Michael G Schwern 143 | 144 | * Fix compile on Solaris. Reported by Jean-Jacques Sarton 145 | 146 | 2008-04-23 Miklos Szeredi 147 | 148 | * Released 2.0 149 | 150 | 2008-04-23 Miklos Szeredi 151 | 152 | * Add manual page. Written by Bartosz Fenski 153 | 154 | 2008-04-22 Miklos Szeredi 155 | 156 | * Add missing ssh options: ControlMaster, ControlPath, 157 | KbdInteractiveAuthentication, KbdInteractiveDevices, LocalCommand, 158 | RekeyLimit (Debian bug #430225). 159 | 160 | * Allow the '-ossh_command=CMD' command to contain parameters. 161 | Escape charater is backslash. 162 | 163 | * Limit write requests to 64kB. 164 | 165 | * Support "statvfs@openssh.com" extension, which will be available 166 | in OpenSSH 5.1. 167 | 168 | 2008-04-21 Miklos Szeredi 169 | 170 | * Fix incorrect disk usage reported by 'du' for files of size 4GB 171 | or above. Reported by Christian Boltz. 172 | 173 | 2008-04-16 Miklos Szeredi 174 | 175 | * If debugging is enabled, print some statistics at exit about the 176 | number of bytes transferred, etc.. 177 | 178 | 2008-03-31 Miklos Szeredi 179 | 180 | * Support "posix-rename@openssh.com" extension available in 181 | OpenSSH 4.9. This allows rename to be atomic even when target 182 | file or directory exists. If available, the extension will be 183 | used instead of the rename operation in the original protocol and 184 | the "-oworkaround=rename" option will be ignored. 185 | 186 | 2008-03-28 Miklos Szeredi 187 | 188 | * Add support for password authentication with pam_mount. 189 | Original patch and help with testing: John S. Skogtvedt 190 | 191 | 2008-03-03 Miklos Szeredi 192 | 193 | * Fix ssh debug only appearing if "-d" is used. Reported by 194 | Michael Gorbach 195 | 196 | 2008-01-03 Miklos Szeredi 197 | 198 | * Fix condition for building of sshnodelay.so 199 | 200 | 2007-12-17 Miklos Szeredi 201 | 202 | * Released 1.9 203 | 204 | 2007-12-12 Miklos Szeredi 205 | 206 | * Fix checking for fuse_opt_parse in configure 207 | 208 | 2007-12-11 Miklos Szeredi 209 | 210 | * Fill in st_ctime attribute with st_mtime, instead of leaving it 211 | zero 212 | 213 | * Check for OpenSSH version >= 4.4, and if found, don't build the 214 | sshnodelay.so workaround 215 | 216 | 2007-12-10 Miklos Szeredi 217 | 218 | * Fix crash on writing files, caused by missing locking around 219 | sshfs_file_get() 220 | 221 | * Don't time requests when not debugging 222 | 223 | * Add subtype support with libfuse >= 2.7.0 224 | 225 | * Abort on allocation failure instead of exit 226 | 227 | * Correctly show default of nodelaysrv workaround in help message 228 | 229 | 2007-05-16 Miklos Szeredi 230 | 231 | * Released 1.8 232 | 233 | 2007-05-15 Miklos Szeredi 234 | 235 | * Add needed g_thread_init() to fix rare crashes. Reported by 236 | Dimitrios Apostolou 237 | 238 | * Fix memory leak in sshfs_open_common() 239 | 240 | 2007-04-18 Miklos Szeredi 241 | 242 | * Fix crash within gdb, caused by sem_wait() returning with an 243 | error on interrupt. Reported by Dimitrios Apostolou 244 | 245 | * Fix spurious cache entries remaining after renaming a directory 246 | 247 | 2007-02-28 Miklos Szeredi 248 | 249 | * Don't set DISPLAY environment variable to "", because it breaks 250 | ssh-askpass. Make nodelaysrv_workaround default to off, because 251 | with this change it may have security implications. 252 | 253 | 2007-02-19 Miklos Szeredi 254 | 255 | * OpenSSH sftp-server can read requests faster, than it processes 256 | them, when it's buffer is full it aborts. This can happen on a 257 | large upload to a slow server. Work around this by limiting the 258 | total size of outstanding reqests. Debian bug #365541. Tracked 259 | down by Thue Janus Kristensen 260 | 261 | * Add --disable-sshnodelay configure option. The sshnodelay.so 262 | hack shouldnt be needed with OpenSSH client versions >= 4.3 263 | 264 | 2006-12-20 Miklos Szeredi 265 | 266 | * Work around write performace issues due to missing TCP_NODELAY 267 | in sshd. Reported by Justin Searle 268 | 269 | 2006-11-10 Miklos Szeredi 270 | 271 | * Fix bug which ommitted directory entries for symlinks with the 272 | -ofollow_symlinks option. Bug reported by Mikael Ståldal 273 | 274 | 2006-09-29 Miklos Szeredi 275 | 276 | * Fix segfault if there are outstanding writes to the server after 277 | release on the file descriptor. This only happened on FreeBSD. 278 | Reported by Andriy Gapon 279 | 280 | 2006-08-18 Miklos Szeredi 281 | 282 | * Released 1.7 283 | 284 | 2006-08-17 Miklos Szeredi 285 | 286 | * Cosmetics on bogus statfs numbers 287 | 288 | 2006-08-16 Miklos Szeredi 289 | 290 | * Add option 'follow_symlinks' to resolve symlinks on the server. 291 | 292 | * On setups where the login shell prints a banner, the sftp 293 | protocol was confused, and bailed out with "reply len too large: 294 | ...". So iterate over any rubbish read immediately after login 295 | until a valid reply is found. 296 | 297 | * Check remote directory for existence before mounting. Closes 298 | Ubuntu bug #46633 299 | 300 | 2006-03-10 Miklos Szeredi 301 | 302 | * Use socketpair() instead of pipe() for communication between ssh 303 | and sshfs. Patch from Csaba Henk 304 | 305 | * Update to FUSE interface version 26 306 | 307 | 2006-03-09 Miklos Szeredi 308 | 309 | * Released 1.6 310 | 311 | 2006-03-09 Miklos Szeredi 312 | 313 | * Fix segfault if remote host is down and reconnection is enabled 314 | 315 | 2006-03-08 Miklos Szeredi 316 | 317 | * Fix bug in symlink transformation. Reported by Ralf Hoffmann 318 | 319 | 2006-02-24 Miklos Szeredi 320 | 321 | * Add workaround for broken truncate on old sftp servers. Can be 322 | enabled with '-oworkaround=truncate'. Patch from Joseph M Link 323 | 324 | 2006-02-23 Miklos Szeredi 325 | 326 | * Avoid double memcpy on writes. Patch by Joseph M Link 327 | 328 | * More memory copy avoidance 329 | 330 | 2006-02-22 Miklos Szeredi 331 | 332 | * Add -fPIC to the comile flags of sshnodelay.so. Reported by 333 | Anthony Kolasny 334 | 335 | 2006-02-20 Miklos Szeredi 336 | 337 | * Released 1.5 338 | 339 | 2006-02-19 Miklos Szeredi 340 | 341 | * Fix compilation with old FUSE versions. Report by Kurt George 342 | Gjerde 343 | 344 | 2006-02-16 Miklos Szeredi 345 | 346 | * Block TERM/INT/HUP/QUIT signals in sshfs reading thread, so they 347 | will always be received by the FUSE main thread. Fixes the 348 | "double ^C" problem seen on FreeBSD. 349 | 350 | * Use 'auto_cache' option if available, so that file cache is not 351 | thrown away on each open. This should make repeated reading of a 352 | file much much faster. 353 | 354 | 2006-01-31 Miklos Szeredi 355 | 356 | * Fix problems with nodelay workaround on FreeBSD. 357 | 358 | 2006-01-30 Miklos Szeredi 359 | 360 | * Fix data consitency bug if readahead is enabled and writes are 361 | intermixed with reads. Solution is far from optimal, since it 362 | will prevent readahead in the above situation. If used with FUSE 363 | >= 2.6.0 with Linux-2.6.X, readahead will be done by the kernel, 364 | and hence there will be no performance penalty. Bug reported and 365 | test program written by Wolfgang Köbler. Further testing on 366 | FreeBSD by Csaba Henk 367 | 368 | 2006-01-29 Miklos Szeredi 369 | 370 | * Add '-olarge_read' option for Linux-2.4.*. This should 371 | generally improve download performance 372 | 373 | 2006-01-27 Miklos Szeredi 374 | 375 | * Add workaround (enabled by default) for ssh clients not setting 376 | TCP_NODELAY on the network connection. Currently this is all 377 | known versions of openssh. This may improve download speed in 378 | some circumstances 379 | 380 | * Make it possible to idividually disable workarounds with a "no" 381 | prefix 382 | 383 | * Change '-otransform_symlinks' not to prefix with the mountpoint 384 | (which is fragile wrt. moving/binding the mount). Rather convert 385 | absolute symlinks to relative if possible. Does not yet work if 386 | base path is itself relative 387 | 388 | 2006-01-25 Miklos Szeredi 389 | 390 | * Use TCP_NODELAY socket option for direct connection. This may 391 | improve download speed in some circumstances 392 | 393 | 2006-01-20 Miklos Szeredi 394 | 395 | * Update to fuse API 2.6 396 | 397 | * Don't do readahead in sshfs if it's done in kernel 398 | 399 | 2006-01-14 Miklos Szeredi 400 | 401 | * Released 1.4 402 | 403 | 2006-01-09 Miklos Szeredi 404 | 405 | * Added 'transform_symlinks' option to "fix-up" absolute symlinks. 406 | Patch by Paul Jarc 407 | 408 | * Add option parsing implementation, so linking with older than 409 | 2.5.0 libfuse still works. 410 | 411 | 2005-12-09 Miklos Szeredi 412 | 413 | * Use new option parsing interface of FUSE 414 | 415 | 2005-11-28 Miklos Szeredi 416 | 417 | * Set statvfs::f_frsize 418 | 419 | 2005-11-23 Miklos Szeredi 420 | 421 | * Fix warnings on 64bit systems. Reported by D. R. Evans 422 | 423 | 2005-11-16 Miklos Szeredi 424 | 425 | * Replace EPROTO with the more portable EIO 426 | 427 | * Fix bug in option parsing. Reported by Csaba Henk 428 | 429 | 2005-10-29 Miklos Szeredi 430 | 431 | * Use FUSE version 25 if available 432 | 433 | * Add include. Reported by Csaba Henk 434 | 435 | 2005-10-29 Miklos Szeredi 436 | 437 | * Released 1.3 438 | 439 | 2005-10-28 Miklos Szeredi 440 | 441 | * Add atomic create+open and ftruncate operation. This should fix 442 | issues with 'cp' and other programs failing with "Permission 443 | denied". To be effective, needs FUSE version 2.5 and kernel 444 | version 2.6.15 (just a guess, since neither of them is released 445 | yet). 446 | 447 | 2005-10-27 Miklos Szeredi 448 | 449 | * Add support for SSH protocol version 1. Bug reported by Miklos 450 | Bagi Jr. 451 | 452 | 2005-10-26 Miklos Szeredi 453 | 454 | * Fix typo (ConnectTimeout -> ConnectionTimeout) in ssh options. 455 | Bug reported by Miklos Bagi Jr. 456 | 457 | 2005-10-19 Miklos Szeredi 458 | 459 | * GNOME Nautilus fails to copy file to sshfs filesystem, because 460 | FUSE returns zero free space. So instead return huge (999999999 461 | kbytes) amount of free space, yet it should be obvious that the 462 | number is artificial. Bug report by Peter Kronheimer 463 | 464 | 2005-10-18 Miklos Szeredi 465 | 466 | * Add remote uid detection and translation ('idmap=user' option). 467 | Idea and implementation details worked out by Jean-Marc Valin 468 | 469 | 2005-10-17 Miklos Szeredi 470 | 471 | * Add one more missing lock. 472 | 473 | * Add workaround for failure to rename to an existing file. Based 474 | on patch by Michael Best 475 | 476 | 2005-10-15 Miklos Szeredi 477 | 478 | * Protect request ID allocation with mutex. Bug report by Tvrtko 479 | Ursulin 480 | 481 | 2005-08-17 Miklos Szeredi 482 | 483 | * Try to calculate approximate disk usage of files from their 484 | size, so that 'du' returns meaningful results. Problem reported 485 | by Bernd Amend. 486 | 487 | 2005-08-15 Miklos Szeredi 488 | 489 | * Released 1.2 490 | 491 | 2005-08-13 Miklos Szeredi 492 | 493 | * Add 'reconnect' option, which tries to reconnect to the server 494 | when the connection is broken. If a password is required for 495 | connection, it is recommended that you install ssh-askpass, and 496 | set the SSH_ASKPASS environment variable (see 'man ssh' for more 497 | details). 498 | 499 | 2005-05-05 Miklos Szeredi 500 | 501 | * Work around missing truncate() support in some older sftp 502 | servers (only works for zero size truncate). Thanks to Eduard 503 | Czimbalmos for the bug report and help with testing 504 | 505 | 2005-04-13 Miklos Szeredi 506 | 507 | * Fix compilation with gcc-2.95. Reported by David A. Gershman 508 | 509 | 2005-03-08 Miklos Szeredi 510 | 511 | * Make it work on server version 2 (e.g. Sun_SSH_1.0.1). Report 512 | and testing by Pieter J. Kersten 513 | 514 | 2005-03-04 Miklos Szeredi 515 | 516 | * Released 1.1 517 | 518 | 2005-03-03 Miklos Szeredi 519 | 520 | * Slightly optimize readahead. Still not clever enough to always 521 | keep the pipe filled. 522 | 523 | * Add 'sshfs_debug' option 524 | 525 | 2005-02-17 Miklos Szeredi 526 | 527 | * Parse 'max_read' mount option and if smaller than 65536 forward 528 | to FUSE 529 | 530 | 2005-02-16 Miklos Szeredi 531 | 532 | * Added simple readahead (big performance gain in case of 533 | sequential read pattern). Can be disabled with '-o no_readahead' 534 | 535 | 2005-02-14 Miklos Szeredi 536 | 537 | * Added asynchronous writeback (big performance gain) and made 538 | this the default. Can be disabled with '-o sshfs_sync' 539 | 540 | 2005-02-09 Miklos Szeredi 541 | 542 | * Added option to start arbitary command instead of 'ssh' 543 | 544 | * Re-added '-p PORT' as a convenience option, also '-C' works as 545 | in ssh. 546 | 547 | 2005-02-08 Miklos Szeredi 548 | 549 | * Add caching of symlinks 550 | 551 | * Add support for many ssh options to be passed to ssh 552 | 553 | * Port number can now actually be specified with "-o port=PORT", 554 | bug spotted by Andrew Ukrainec 555 | 556 | 2005-02-07 Miklos Szeredi 557 | 558 | * Separate attribute caching to a separate layer 559 | 560 | * Add caching of directory contents 561 | 562 | 2005-02-03 Miklos Szeredi 563 | 564 | Fix PKG_CONFIG_PATH setting in configure.ac (reported by Alpar 565 | Juttner) 566 | 567 | 2005-01-09 Miklos Szeredi 568 | 569 | * Released 1.0 570 | 571 | 2004-12-04 Miklos Szeredi 572 | 573 | * Started ChangeLog 574 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this file with automake to produce Makefile.in 2 | 3 | bin_PROGRAMS = sshfs 4 | 5 | sshfs_SOURCES = sshfs.c cache.c cache.h compat/darwin_semaphore.h compat/darwin_semaphore.c 6 | if FUSE_OPT_COMPAT 7 | sshfs_SOURCES += compat/fuse_opt.c compat/fuse_opt.h 8 | endif 9 | 10 | sshfs_LDADD = $(SSHFS_LIBS) 11 | sshfs_CFLAGS = $(SSHFS_CFLAGS) 12 | sshfs_CPPFLAGS = -D_REENTRANT -DFUSE_USE_VERSION=26 -DLIBDIR=\"$(libdir)\" -Icompat 13 | 14 | EXTRA_DIST = sshnodelay.c FAQ.txt 15 | CLEANFILES = sshnodelay.so 16 | 17 | dist_man_MANS = sshfs.1 18 | 19 | if SSH_NODELAY_SO 20 | all-local: sshnodelay.so 21 | 22 | install-exec-local: sshnodelay.so 23 | test -z "$(libdir)" || $(mkdir_p) "$(DESTDIR)$(libdir)" 24 | $(INSTALL) -m 755 sshnodelay.so "$(DESTDIR)$(libdir)/sshnodelay.so" 25 | 26 | uninstall-local: 27 | rm -f "$(DESTDIR)$(libdir)/sshnodelay.so" 28 | 29 | sshnodelay.so: 30 | $(CC) -Wall -W -s --shared -fPIC $(sshnodelay_libs) sshnodelay.c -o sshnodelay.so 31 | endif 32 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | What is new in 2.1, 2.2 and 2.3 2 | ------------------------------- 3 | 4 | * Support hard link creation if server is OpenSSH 5.7 or later 5 | 6 | * Check mount point and options before connecting to ssh server 7 | 8 | * Handle commas in usernames 9 | 10 | * Small improvements and bug fixes 11 | 12 | 13 | What is new in 2.0 14 | ------------------ 15 | 16 | * Support password authentication with pam_mount 17 | 18 | * Support atomic renames if server is OpenSSH 4.9 or later 19 | 20 | * Support getting disk usage if server is OpenSSH 5.1 or later 21 | 22 | * Small enhancements and bug fixes 23 | 24 | What is new in 1.9 25 | ------------------ 26 | 27 | * Fix a serious bug, that could result in sshfs hanging, crashing, or 28 | reporting out-of-memory 29 | 30 | What is new in 1.8 31 | ------------------ 32 | 33 | * Bug fixes 34 | 35 | What is new in 1.7 36 | ------------------ 37 | 38 | * Tolerate servers which print a banner on login 39 | 40 | * Small improvements 41 | 42 | What is new in 1.6 43 | ------------------ 44 | 45 | * Workaround for missing truncate operation on old sftp servers 46 | 47 | * Bug fixes 48 | 49 | What is new in 1.5 50 | ------------------ 51 | 52 | * Improvements to read performance. Now both read and write 53 | throughput should be very close to 'scp' 54 | 55 | * If used with FUSE 2.6.0 or later, then perform better data caching. 56 | This should show dramatic speed improvements when a file is opened 57 | more than once 58 | 59 | * Bug fixes 60 | 61 | What is new in 1.4 62 | ------------------ 63 | 64 | * Updated to version 25 of libfuse API 65 | 66 | * This means that the 'cp' of readonly file to sshfs bug is finally 67 | solved (as long as using libfuse 2.5.0 or later *and* Linux 2.6.15 68 | or later) 69 | 70 | * Sshfs now works on FreeBSD 71 | 72 | * Added option to "transform" absolute symbolic links 73 | 74 | What is new in 1.3 75 | ------------------ 76 | 77 | * Add workaround for failure to rename to an existing file 78 | 79 | * Simple user ID mapping 80 | 81 | * Estimate disk usage of files based on size 82 | 83 | * Report "infinite" disk space 84 | 85 | * Bug fixes 86 | 87 | What is new in 1.2 88 | ------------------ 89 | 90 | * Better compatibility with different sftp servers 91 | 92 | * Automatic reconnect (optional) 93 | 94 | What is new in 1.1 95 | ------------------ 96 | 97 | * Performance improvements: 98 | 99 | - directory content caching 100 | 101 | - symlink caching 102 | 103 | - asynchronous writeback 104 | 105 | - readahead 106 | 107 | * Fixed '-p' option 108 | 109 | What is new in 1.0 110 | ------------------ 111 | 112 | * Initial release 113 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Abstract 2 | ======== 3 | 4 | This is a filesystem client based on the SSH File Transfer Protocol. 5 | Since most SSH servers already support this protocol it is very easy 6 | to set up: i.e. on the server side there's nothing to do. On the 7 | client side mounting the filesystem is as easy as logging into the 8 | server with ssh. 9 | 10 | The idea of sshfs was taken from the SSHFS filesystem distributed with 11 | LUFS, which I found very useful. There were some limitations of that 12 | codebase, so I rewrote it. Features of this implementation are: 13 | 14 | - Based on FUSE (the best userspace filesystem framework for Linux ;) 15 | 16 | - Multithreading: more than one request can be on it's way to the 17 | server 18 | 19 | - Allowing large reads (max 64k) 20 | 21 | - Caching directory contents 22 | 23 | - Reconnect on failure 24 | 25 | Latest version 26 | ============== 27 | 28 | The latest version and more information can be found on 29 | 30 | http://fuse.sourceforge.net/sshfs.html 31 | 32 | 33 | How to mount a filesystem 34 | ========================= 35 | 36 | Once sshfs is installed (see next section) running it is very simple: 37 | 38 | sshfs hostname: mountpoint 39 | 40 | Note, that it's recommended to run it as user, not as root. For this 41 | to work the mountpoint must be owned by the user. If the username is 42 | different on the host you are connecting to, then use the 43 | "username@host:" form. If you need to enter a password sshfs will ask 44 | for it (actually it just runs ssh which ask for the password if 45 | needed). You can also specify a directory after the ":". The default 46 | is the home directory. 47 | 48 | Also many ssh options can be specified (see the manual pages for 49 | sftp(1) and ssh_config(5)), including the remote port number 50 | ('-oport=PORT') 51 | 52 | To unmount the filesystem: 53 | 54 | fusermount -u moutpoint 55 | 56 | 57 | Installing 58 | ========== 59 | 60 | First you need to download FUSE 2.2 or later from: 61 | 62 | http://fuse.sourceforge.net 63 | 64 | You also need to install the devel package for glib2.0. After 65 | installing FUSE, compile sshfs the usual way: 66 | 67 | ./configure 68 | make 69 | make install (as root) 70 | 71 | And you are ready to go. 72 | 73 | If checking out from CVS for the first time also do 'autoreconf -i' 74 | before doing './configure'. 75 | -------------------------------------------------------------------------------- /build.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Possible flags are: 3 | # --release build this module for final distribution 4 | # --root DIR install the binary into this directory. If this flag is not set - the script 5 | # redeploys kext to local machine and restarts it 6 | 7 | require 'fileutils' 8 | 9 | CWD = File.dirname(__FILE__) 10 | Dir.chdir(CWD) 11 | 12 | release = ARGV.include?('--release') 13 | root_dir = ARGV.index('--root') ? ARGV[ARGV.index('--root') + 1] : nil 14 | 15 | abort("root directory #{root_dir} does not exist") if ARGV.index('--root') and not File.exists?(root_dir) 16 | 17 | system('git clean -xdf') if release 18 | 19 | unless File.exists?('Makefile') then 20 | flags = '' 21 | if release then 22 | flags += "CFLAGS='-mmacosx-version-min=10.5'" 23 | else 24 | flags += "CFLAGS='-g -O0'" 25 | end 26 | 27 | system("autoreconf -f -i -Wall,no-obsolete") or abort 28 | system("./configure #{flags}") or abort 29 | end 30 | 31 | tmp_dir = "/tmp/sshfsbuild-#{Process.pid}" 32 | Dir.mkdir(tmp_dir) 33 | 34 | ld_flags = '' 35 | dylibs = %w(iconv gthread-2.0 glib-2.0 intl) 36 | if release 37 | # In case if we build the distribution we need statically link against 38 | # macports libraries from the 'dylibs' list above. 39 | # To do it - we trick the build system by adding temp path with static libraries. 40 | for lib in dylibs do 41 | `ln -s /opt/local/lib/lib#{lib}.a #{tmp_dir}/` 42 | end 43 | 44 | ld_flags = "LDFLAGS='-L#{tmp_dir} -framework CoreFoundation -framework CoreServices'" 45 | end 46 | 47 | system("make -s -j3 #{ld_flags}") or abort 48 | 49 | cmd = 'sudo make install' 50 | if root_dir 51 | cmd = cmd + ' DESTDIR=' + root_dir 52 | end 53 | 54 | system(cmd) 55 | 56 | FileUtils.rm_rf(tmp_dir) 57 | -------------------------------------------------------------------------------- /cache.c: -------------------------------------------------------------------------------- 1 | /* 2 | Caching file system proxy 3 | Copyright (C) 2004 Miklos Szeredi 4 | 5 | This program can be distributed under the terms of the GNU GPL. 6 | See the file COPYING. 7 | */ 8 | 9 | #include "cache.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define DEFAULT_CACHE_TIMEOUT 20 18 | #define MAX_CACHE_SIZE 10000 19 | #define MIN_CACHE_CLEAN_INTERVAL 5 20 | #define CACHE_CLEAN_INTERVAL 60 21 | 22 | struct cache { 23 | int on; 24 | unsigned stat_timeout; 25 | unsigned dir_timeout; 26 | unsigned link_timeout; 27 | struct fuse_cache_operations *next_oper; 28 | GHashTable *table; 29 | pthread_mutex_t lock; 30 | time_t last_cleaned; 31 | uint64_t write_ctr; 32 | }; 33 | 34 | static struct cache cache; 35 | 36 | struct node { 37 | struct stat stat; 38 | time_t stat_valid; 39 | char **dir; 40 | time_t dir_valid; 41 | char *link; 42 | time_t link_valid; 43 | time_t valid; 44 | }; 45 | 46 | struct fuse_cache_dirhandle { 47 | const char *path; 48 | fuse_dirh_t h; 49 | fuse_dirfil_t filler; 50 | GPtrArray *dir; 51 | uint64_t wrctr; 52 | }; 53 | 54 | static void free_node(gpointer node_) 55 | { 56 | struct node *node = (struct node *) node_; 57 | g_strfreev(node->dir); 58 | g_free(node); 59 | } 60 | 61 | static int cache_clean_entry(void *key_, struct node *node, time_t *now) 62 | { 63 | (void) key_; 64 | if (*now > node->valid) 65 | return TRUE; 66 | else 67 | return FALSE; 68 | } 69 | 70 | static void cache_clean(void) 71 | { 72 | time_t now = time(NULL); 73 | if (now > cache.last_cleaned + MIN_CACHE_CLEAN_INTERVAL && 74 | (g_hash_table_size(cache.table) > MAX_CACHE_SIZE || 75 | now > cache.last_cleaned + CACHE_CLEAN_INTERVAL)) { 76 | g_hash_table_foreach_remove(cache.table, 77 | (GHRFunc) cache_clean_entry, &now); 78 | cache.last_cleaned = now; 79 | } 80 | } 81 | 82 | static struct node *cache_lookup(const char *path) 83 | { 84 | return (struct node *) g_hash_table_lookup(cache.table, path); 85 | } 86 | 87 | static void cache_purge(const char *path) 88 | { 89 | g_hash_table_remove(cache.table, path); 90 | } 91 | 92 | static void cache_purge_parent(const char *path) 93 | { 94 | const char *s = strrchr(path, '/'); 95 | if (s) { 96 | if (s == path) 97 | g_hash_table_remove(cache.table, "/"); 98 | else { 99 | char *parent = g_strndup(path, s - path); 100 | cache_purge(parent); 101 | g_free(parent); 102 | } 103 | } 104 | } 105 | 106 | void cache_invalidate(const char *path) 107 | { 108 | if (!cache.on) 109 | return; 110 | 111 | pthread_mutex_lock(&cache.lock); 112 | cache_purge(path); 113 | pthread_mutex_unlock(&cache.lock); 114 | } 115 | 116 | void cache_invalidate_write(const char *path) 117 | { 118 | pthread_mutex_lock(&cache.lock); 119 | cache_purge(path); 120 | cache.write_ctr++; 121 | pthread_mutex_unlock(&cache.lock); 122 | } 123 | 124 | static void cache_invalidate_dir(const char *path) 125 | { 126 | pthread_mutex_lock(&cache.lock); 127 | cache_purge(path); 128 | cache_purge_parent(path); 129 | pthread_mutex_unlock(&cache.lock); 130 | } 131 | 132 | static int cache_del_children(const char *key, void *val_, const char *path) 133 | { 134 | (void) val_; 135 | if (strncmp(key, path, strlen(path)) == 0) 136 | return TRUE; 137 | else 138 | return FALSE; 139 | } 140 | 141 | static void cache_do_rename(const char *from, const char *to) 142 | { 143 | pthread_mutex_lock(&cache.lock); 144 | g_hash_table_foreach_remove(cache.table, (GHRFunc) cache_del_children, 145 | (char *) from); 146 | cache_purge(from); 147 | cache_purge(to); 148 | cache_purge_parent(from); 149 | cache_purge_parent(to); 150 | pthread_mutex_unlock(&cache.lock); 151 | } 152 | 153 | static struct node *cache_get(const char *path) 154 | { 155 | struct node *node = cache_lookup(path); 156 | if (node == NULL) { 157 | char *pathcopy = g_strdup(path); 158 | node = g_new0(struct node, 1); 159 | g_hash_table_insert(cache.table, pathcopy, node); 160 | } 161 | return node; 162 | } 163 | 164 | void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr) 165 | { 166 | struct node *node; 167 | 168 | if (!cache.on) 169 | return; 170 | 171 | pthread_mutex_lock(&cache.lock); 172 | if (wrctr == cache.write_ctr) { 173 | node = cache_get(path); 174 | node->stat = *stbuf; 175 | node->stat_valid = time(NULL) + cache.stat_timeout; 176 | if (node->stat_valid > node->valid) 177 | node->valid = node->stat_valid; 178 | cache_clean(); 179 | } 180 | pthread_mutex_unlock(&cache.lock); 181 | } 182 | 183 | static void cache_add_dir(const char *path, char **dir) 184 | { 185 | struct node *node; 186 | 187 | pthread_mutex_lock(&cache.lock); 188 | node = cache_get(path); 189 | g_strfreev(node->dir); 190 | node->dir = dir; 191 | node->dir_valid = time(NULL) + cache.dir_timeout; 192 | if (node->dir_valid > node->valid) 193 | node->valid = node->dir_valid; 194 | cache_clean(); 195 | pthread_mutex_unlock(&cache.lock); 196 | } 197 | 198 | static size_t my_strnlen(const char *s, size_t maxsize) 199 | { 200 | const char *p; 201 | for (p = s; maxsize && *p; maxsize--, p++); 202 | return p - s; 203 | } 204 | 205 | static void cache_add_link(const char *path, const char *link, size_t size) 206 | { 207 | struct node *node; 208 | 209 | pthread_mutex_lock(&cache.lock); 210 | node = cache_get(path); 211 | g_free(node->link); 212 | node->link = g_strndup(link, my_strnlen(link, size-1)); 213 | node->link_valid = time(NULL) + cache.link_timeout; 214 | if (node->link_valid > node->valid) 215 | node->valid = node->link_valid; 216 | cache_clean(); 217 | pthread_mutex_unlock(&cache.lock); 218 | } 219 | 220 | static int cache_get_attr(const char *path, struct stat *stbuf) 221 | { 222 | struct node *node; 223 | int err = -EAGAIN; 224 | pthread_mutex_lock(&cache.lock); 225 | node = cache_lookup(path); 226 | if (node != NULL) { 227 | time_t now = time(NULL); 228 | if (node->stat_valid - now >= 0) { 229 | *stbuf = node->stat; 230 | err = 0; 231 | } 232 | } 233 | pthread_mutex_unlock(&cache.lock); 234 | return err; 235 | } 236 | 237 | uint64_t cache_get_write_ctr(void) 238 | { 239 | uint64_t res; 240 | 241 | pthread_mutex_lock(&cache.lock); 242 | res = cache.write_ctr; 243 | pthread_mutex_unlock(&cache.lock); 244 | 245 | return res; 246 | } 247 | 248 | static int cache_getattr(const char *path, struct stat *stbuf) 249 | { 250 | int err = cache_get_attr(path, stbuf); 251 | if (err) { 252 | uint64_t wrctr = cache_get_write_ctr(); 253 | err = cache.next_oper->oper.getattr(path, stbuf); 254 | if (!err) 255 | cache_add_attr(path, stbuf, wrctr); 256 | } 257 | return err; 258 | } 259 | 260 | static int cache_readlink(const char *path, char *buf, size_t size) 261 | { 262 | struct node *node; 263 | int err; 264 | 265 | pthread_mutex_lock(&cache.lock); 266 | node = cache_lookup(path); 267 | if (node != NULL) { 268 | time_t now = time(NULL); 269 | if (node->link_valid - now >= 0) { 270 | strncpy(buf, node->link, size-1); 271 | buf[size-1] = '\0'; 272 | pthread_mutex_unlock(&cache.lock); 273 | return 0; 274 | } 275 | } 276 | pthread_mutex_unlock(&cache.lock); 277 | err = cache.next_oper->oper.readlink(path, buf, size); 278 | if (!err) 279 | cache_add_link(path, buf, size); 280 | 281 | return err; 282 | } 283 | 284 | static int cache_dirfill(fuse_cache_dirh_t ch, const char *name, 285 | const struct stat *stbuf) 286 | { 287 | int err = ch->filler(ch->h, name, 0, 0); 288 | if (!err) { 289 | g_ptr_array_add(ch->dir, g_strdup(name)); 290 | if (stbuf->st_mode & S_IFMT) { 291 | char *fullpath; 292 | const char *basepath = !ch->path[1] ? "" : ch->path; 293 | 294 | fullpath = g_strdup_printf("%s/%s", basepath, name); 295 | cache_add_attr(fullpath, stbuf, ch->wrctr); 296 | g_free(fullpath); 297 | } 298 | } 299 | return err; 300 | } 301 | 302 | static int cache_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler) 303 | { 304 | struct fuse_cache_dirhandle ch; 305 | int err; 306 | char **dir; 307 | struct node *node; 308 | 309 | pthread_mutex_lock(&cache.lock); 310 | node = cache_lookup(path); 311 | if (node != NULL && node->dir != NULL) { 312 | time_t now = time(NULL); 313 | if (node->dir_valid - now >= 0) { 314 | for(dir = node->dir; *dir != NULL; dir++) 315 | filler(h, *dir, 0, 0); 316 | pthread_mutex_unlock(&cache.lock); 317 | return 0; 318 | } 319 | } 320 | pthread_mutex_unlock(&cache.lock); 321 | 322 | ch.path = path; 323 | ch.h = h; 324 | ch.filler = filler; 325 | ch.dir = g_ptr_array_new(); 326 | ch.wrctr = cache_get_write_ctr(); 327 | err = cache.next_oper->cache_getdir(path, &ch, cache_dirfill); 328 | g_ptr_array_add(ch.dir, NULL); 329 | dir = (char **) ch.dir->pdata; 330 | if (!err) 331 | cache_add_dir(path, dir); 332 | else 333 | g_strfreev(dir); 334 | g_ptr_array_free(ch.dir, FALSE); 335 | return err; 336 | } 337 | 338 | static int cache_unity_dirfill(fuse_cache_dirh_t ch, const char *name, 339 | const struct stat *stbuf) 340 | { 341 | (void) stbuf; 342 | return ch->filler(ch->h, name, 0, 0); 343 | } 344 | 345 | static int cache_unity_getdir(const char *path, fuse_dirh_t h, 346 | fuse_dirfil_t filler) 347 | { 348 | struct fuse_cache_dirhandle ch; 349 | ch.h = h; 350 | ch.filler = filler; 351 | return cache.next_oper->cache_getdir(path, &ch, cache_unity_dirfill); 352 | } 353 | 354 | static int cache_mknod(const char *path, mode_t mode, dev_t rdev) 355 | { 356 | int err = cache.next_oper->oper.mknod(path, mode, rdev); 357 | if (!err) 358 | cache_invalidate_dir(path); 359 | return err; 360 | } 361 | 362 | static int cache_mkdir(const char *path, mode_t mode) 363 | { 364 | int err = cache.next_oper->oper.mkdir(path, mode); 365 | if (!err) 366 | cache_invalidate_dir(path); 367 | return err; 368 | } 369 | 370 | static int cache_unlink(const char *path) 371 | { 372 | int err = cache.next_oper->oper.unlink(path); 373 | if (!err) 374 | cache_invalidate_dir(path); 375 | return err; 376 | } 377 | 378 | static int cache_rmdir(const char *path) 379 | { 380 | int err = cache.next_oper->oper.rmdir(path); 381 | if (!err) 382 | cache_invalidate_dir(path); 383 | return err; 384 | } 385 | 386 | static int cache_symlink(const char *from, const char *to) 387 | { 388 | int err = cache.next_oper->oper.symlink(from, to); 389 | if (!err) 390 | cache_invalidate_dir(to); 391 | return err; 392 | } 393 | 394 | static int cache_rename(const char *from, const char *to) 395 | { 396 | int err = cache.next_oper->oper.rename(from, to); 397 | if (!err) 398 | cache_do_rename(from, to); 399 | return err; 400 | } 401 | 402 | static int cache_link(const char *from, const char *to) 403 | { 404 | int err = cache.next_oper->oper.link(from, to); 405 | if (!err) { 406 | cache_invalidate(from); 407 | cache_invalidate_dir(to); 408 | } 409 | return err; 410 | } 411 | 412 | static int cache_chmod(const char *path, mode_t mode) 413 | { 414 | int err = cache.next_oper->oper.chmod(path, mode); 415 | if (!err) 416 | cache_invalidate(path); 417 | return err; 418 | } 419 | 420 | static int cache_chown(const char *path, uid_t uid, gid_t gid) 421 | { 422 | int err = cache.next_oper->oper.chown(path, uid, gid); 423 | if (!err) 424 | cache_invalidate(path); 425 | return err; 426 | } 427 | 428 | static int cache_truncate(const char *path, off_t size) 429 | { 430 | int err = cache.next_oper->oper.truncate(path, size); 431 | if (!err) 432 | cache_invalidate(path); 433 | return err; 434 | } 435 | 436 | static int cache_utime(const char *path, struct utimbuf *buf) 437 | { 438 | int err = cache.next_oper->oper.utime(path, buf); 439 | if (!err) 440 | cache_invalidate(path); 441 | return err; 442 | } 443 | 444 | static int cache_write(const char *path, const char *buf, size_t size, 445 | off_t offset, struct fuse_file_info *fi) 446 | { 447 | int res = cache.next_oper->oper.write(path, buf, size, offset, fi); 448 | if (res >= 0) 449 | cache_invalidate_write(path); 450 | return res; 451 | } 452 | 453 | #if FUSE_VERSION >= 25 454 | static int cache_create(const char *path, mode_t mode, 455 | struct fuse_file_info *fi) 456 | { 457 | int err = cache.next_oper->oper.create(path, mode, fi); 458 | if (!err) 459 | cache_invalidate_dir(path); 460 | return err; 461 | } 462 | 463 | static int cache_ftruncate(const char *path, off_t size, 464 | struct fuse_file_info *fi) 465 | { 466 | int err = cache.next_oper->oper.ftruncate(path, size, fi); 467 | if (!err) 468 | cache_invalidate(path); 469 | return err; 470 | } 471 | 472 | static int cache_fgetattr(const char *path, struct stat *stbuf, 473 | struct fuse_file_info *fi) 474 | { 475 | int err = cache_get_attr(path, stbuf); 476 | if (err) { 477 | uint64_t wrctr = cache_get_write_ctr(); 478 | err = cache.next_oper->oper.fgetattr(path, stbuf, fi); 479 | if (!err) 480 | cache_add_attr(path, stbuf, wrctr); 481 | } 482 | return err; 483 | } 484 | #endif 485 | 486 | static void cache_unity_fill(struct fuse_cache_operations *oper, 487 | struct fuse_operations *cache_oper) 488 | { 489 | #if FUSE_VERSION >= 23 490 | cache_oper->init = oper->oper.init; 491 | #endif 492 | cache_oper->getattr = oper->oper.getattr; 493 | cache_oper->readlink = oper->oper.readlink; 494 | cache_oper->getdir = cache_unity_getdir; 495 | cache_oper->mknod = oper->oper.mknod; 496 | cache_oper->mkdir = oper->oper.mkdir; 497 | cache_oper->symlink = oper->oper.symlink; 498 | cache_oper->unlink = oper->oper.unlink; 499 | cache_oper->rmdir = oper->oper.rmdir; 500 | cache_oper->rename = oper->oper.rename; 501 | cache_oper->link = oper->oper.link; 502 | cache_oper->chmod = oper->oper.chmod; 503 | cache_oper->chown = oper->oper.chown; 504 | cache_oper->truncate = oper->oper.truncate; 505 | cache_oper->utime = oper->oper.utime; 506 | cache_oper->open = oper->oper.open; 507 | cache_oper->read = oper->oper.read; 508 | cache_oper->write = oper->oper.write; 509 | cache_oper->flush = oper->oper.flush; 510 | cache_oper->release = oper->oper.release; 511 | cache_oper->fsync = oper->oper.fsync; 512 | cache_oper->statfs = oper->oper.statfs; 513 | cache_oper->setxattr = oper->oper.setxattr; 514 | cache_oper->getxattr = oper->oper.getxattr; 515 | cache_oper->listxattr = oper->oper.listxattr; 516 | cache_oper->removexattr = oper->oper.removexattr; 517 | #if FUSE_VERSION >= 25 518 | cache_oper->create = oper->oper.create; 519 | cache_oper->ftruncate = oper->oper.ftruncate; 520 | cache_oper->fgetattr = oper->oper.fgetattr; 521 | #endif 522 | } 523 | 524 | static void cache_fill(struct fuse_cache_operations *oper, 525 | struct fuse_operations *cache_oper) 526 | { 527 | cache_oper->getattr = oper->oper.getattr ? cache_getattr : NULL; 528 | cache_oper->readlink = oper->oper.readlink ? cache_readlink : NULL; 529 | cache_oper->getdir = oper->cache_getdir ? cache_getdir : NULL; 530 | cache_oper->mknod = oper->oper.mknod ? cache_mknod : NULL; 531 | cache_oper->mkdir = oper->oper.mkdir ? cache_mkdir : NULL; 532 | cache_oper->symlink = oper->oper.symlink ? cache_symlink : NULL; 533 | cache_oper->unlink = oper->oper.unlink ? cache_unlink : NULL; 534 | cache_oper->rmdir = oper->oper.rmdir ? cache_rmdir : NULL; 535 | cache_oper->rename = oper->oper.rename ? cache_rename : NULL; 536 | cache_oper->link = oper->oper.link ? cache_link : NULL; 537 | cache_oper->chmod = oper->oper.chmod ? cache_chmod : NULL; 538 | cache_oper->chown = oper->oper.chown ? cache_chown : NULL; 539 | cache_oper->truncate = oper->oper.truncate ? cache_truncate : NULL; 540 | cache_oper->utime = oper->oper.utime ? cache_utime : NULL; 541 | cache_oper->write = oper->oper.write ? cache_write : NULL; 542 | #if FUSE_VERSION >= 25 543 | cache_oper->create = oper->oper.create ? cache_create : NULL; 544 | cache_oper->ftruncate = oper->oper.ftruncate ? cache_ftruncate : NULL; 545 | cache_oper->fgetattr = oper->oper.fgetattr ? cache_fgetattr : NULL; 546 | #endif 547 | 548 | } 549 | 550 | struct fuse_operations *cache_init(struct fuse_cache_operations *oper) 551 | { 552 | static struct fuse_operations cache_oper; 553 | cache.next_oper = oper; 554 | 555 | cache_unity_fill(oper, &cache_oper); 556 | #ifdef __APPLE__ 557 | cache_enabled = cache.on; 558 | #endif 559 | if (cache.on) { 560 | cache_fill(oper, &cache_oper); 561 | pthread_mutex_init(&cache.lock, NULL); 562 | cache.table = g_hash_table_new_full(g_str_hash, g_str_equal, 563 | g_free, free_node); 564 | if (cache.table == NULL) { 565 | fprintf(stderr, "failed to create cache\n"); 566 | return NULL; 567 | } 568 | } 569 | return &cache_oper; 570 | } 571 | 572 | static const struct fuse_opt cache_opts[] = { 573 | { "cache=yes", offsetof(struct cache, on), 1 }, 574 | { "cache=no", offsetof(struct cache, on), 0 }, 575 | { "cache_timeout=%u", offsetof(struct cache, stat_timeout), 0 }, 576 | { "cache_timeout=%u", offsetof(struct cache, dir_timeout), 0 }, 577 | { "cache_timeout=%u", offsetof(struct cache, link_timeout), 0 }, 578 | { "cache_stat_timeout=%u", offsetof(struct cache, stat_timeout), 0 }, 579 | { "cache_dir_timeout=%u", offsetof(struct cache, dir_timeout), 0 }, 580 | { "cache_link_timeout=%u", offsetof(struct cache, link_timeout), 0 }, 581 | FUSE_OPT_END 582 | }; 583 | 584 | int cache_parse_options(struct fuse_args *args) 585 | { 586 | cache.stat_timeout = DEFAULT_CACHE_TIMEOUT; 587 | cache.dir_timeout = DEFAULT_CACHE_TIMEOUT; 588 | cache.link_timeout = DEFAULT_CACHE_TIMEOUT; 589 | cache.on = 1; 590 | 591 | return fuse_opt_parse(args, &cache, cache_opts, NULL); 592 | } 593 | 594 | #ifdef __APPLE__ 595 | int cache_enabled; 596 | #endif 597 | -------------------------------------------------------------------------------- /cache.h: -------------------------------------------------------------------------------- 1 | /* 2 | Caching file system proxy 3 | Copyright (C) 2004 Miklos Szeredi 4 | 5 | This program can be distributed under the terms of the GNU GPL. 6 | See the file COPYING. 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #ifndef FUSE_VERSION 13 | #define FUSE_VERSION (FUSE_MAJOR_VERSION * 10 + FUSE_MINOR_VERSION) 14 | #endif 15 | 16 | typedef struct fuse_cache_dirhandle *fuse_cache_dirh_t; 17 | typedef int (*fuse_cache_dirfil_t) (fuse_cache_dirh_t h, const char *name, 18 | const struct stat *stbuf); 19 | 20 | struct fuse_cache_operations { 21 | struct fuse_operations oper; 22 | int (*cache_getdir) (const char *, fuse_cache_dirh_t, fuse_cache_dirfil_t); 23 | }; 24 | 25 | struct fuse_operations *cache_init(struct fuse_cache_operations *oper); 26 | int cache_parse_options(struct fuse_args *args); 27 | void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr); 28 | void cache_invalidate(const char *path); 29 | uint64_t cache_get_write_ctr(void); 30 | 31 | #ifdef __APPLE__ 32 | extern int cache_enabled; 33 | #endif 34 | -------------------------------------------------------------------------------- /compat/darwin_semaphore.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2000,02 Free Software Foundation, Inc. 3 | * This file is part of the GNU C Library. 4 | * Written by Gal Le Mignot 5 | * 6 | * The GNU C Library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Library General Public License as 8 | * published by the Free Software Foundation; either version 2 of the 9 | * License, or (at your option) any later version. 10 | * 11 | * The GNU C Library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Library General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Library General Public 17 | * License along with the GNU C Library; see the file COPYING.LIB. If not, 18 | * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | * Boston, MA 02111-1307, USA. 20 | */ 21 | 22 | #include "darwin_semaphore.h" 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #define __SEM_ID_NONE 0x0 29 | #define __SEM_ID_LOCAL 0xcafef00d 30 | 31 | /* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_init.html */ 32 | int 33 | compat_sem_init(compat_sem_t *sem, int pshared, unsigned int value) 34 | { 35 | if (pshared) { 36 | errno = ENOSYS; 37 | return -1; 38 | } 39 | 40 | sem->id = __SEM_ID_NONE; 41 | 42 | if (pthread_cond_init(&sem->__data.local.count_cond, NULL)) { 43 | goto cond_init_fail; 44 | } 45 | 46 | if (pthread_mutex_init(&sem->__data.local.count_lock, NULL)) { 47 | goto mutex_init_fail; 48 | } 49 | 50 | sem->__data.local.count = value; 51 | sem->id = __SEM_ID_LOCAL; 52 | 53 | return 0; 54 | 55 | mutex_init_fail: 56 | 57 | pthread_cond_destroy(&sem->__data.local.count_cond); 58 | 59 | cond_init_fail: 60 | 61 | return -1; 62 | } 63 | 64 | /* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_destroy.html */ 65 | int 66 | compat_sem_destroy(compat_sem_t *sem) 67 | { 68 | int res = 0; 69 | 70 | pthread_mutex_lock(&sem->__data.local.count_lock); 71 | 72 | sem->id = __SEM_ID_NONE; 73 | pthread_cond_broadcast(&sem->__data.local.count_cond); 74 | 75 | if (pthread_cond_destroy(&sem->__data.local.count_cond)) { 76 | res = -1; 77 | } 78 | 79 | pthread_mutex_unlock(&sem->__data.local.count_lock); 80 | 81 | if (pthread_mutex_destroy(&sem->__data.local.count_lock)) { 82 | res = -1; 83 | } 84 | 85 | return res; 86 | } 87 | 88 | int 89 | compat_sem_getvalue(compat_sem_t *sem, unsigned int *sval) 90 | { 91 | int res = 0; 92 | 93 | pthread_mutex_lock(&sem->__data.local.count_lock); 94 | 95 | if (sem->id != __SEM_ID_LOCAL) { 96 | res = -1; 97 | errno = EINVAL; 98 | } else { 99 | *sval = sem->__data.local.count; 100 | } 101 | 102 | pthread_mutex_unlock(&sem->__data.local.count_lock); 103 | 104 | return res; 105 | } 106 | 107 | /* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_post.html */ 108 | int 109 | compat_sem_post(compat_sem_t *sem) 110 | { 111 | int res = 0; 112 | 113 | pthread_mutex_lock(&sem->__data.local.count_lock); 114 | 115 | if (sem->id != __SEM_ID_LOCAL) { 116 | res = -1; 117 | errno = EINVAL; 118 | } else if (sem->__data.local.count < COMPAT_SEM_VALUE_MAX) { 119 | sem->__data.local.count++; 120 | if (sem->__data.local.count == 1) { 121 | pthread_cond_signal(&sem->__data.local.count_cond); 122 | } 123 | } else { 124 | errno = ERANGE; 125 | res = -1; 126 | } 127 | 128 | pthread_mutex_unlock(&sem->__data.local.count_lock); 129 | 130 | return res; 131 | } 132 | 133 | /* http://www.opengroup.org/onlinepubs/009695399/functions/sem_timedwait.html */ 134 | int 135 | compat_sem_timedwait(compat_sem_t *sem, const struct timespec *abs_timeout) 136 | { 137 | int res = 0; 138 | 139 | if (abs_timeout && 140 | (abs_timeout->tv_nsec < 0 || abs_timeout->tv_nsec >= 1000000000)) { 141 | errno = EINVAL; 142 | return -1; 143 | } 144 | 145 | pthread_cleanup_push((void(*)(void*))&pthread_mutex_unlock, 146 | &sem->__data.local.count_lock); 147 | 148 | pthread_mutex_lock(&sem->__data.local.count_lock); 149 | 150 | if (sem->id != __SEM_ID_LOCAL) { 151 | errno = EINVAL; 152 | res = -1; 153 | } else { 154 | if (!sem->__data.local.count) { 155 | res = pthread_cond_timedwait(&sem->__data.local.count_cond, 156 | &sem->__data.local.count_lock, 157 | abs_timeout); 158 | } 159 | if (res) { 160 | assert(res == ETIMEDOUT); 161 | res = -1; 162 | errno = ETIMEDOUT; 163 | } else if (sem->id != __SEM_ID_LOCAL) { 164 | res = -1; 165 | errno = EINVAL; 166 | } else { 167 | sem->__data.local.count--; 168 | } 169 | } 170 | 171 | pthread_cleanup_pop(1); 172 | 173 | return res; 174 | } 175 | 176 | /* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_trywait.html */ 177 | int 178 | compat_sem_trywait(compat_sem_t *sem) 179 | { 180 | int res = 0; 181 | 182 | pthread_mutex_lock(&sem->__data.local.count_lock); 183 | 184 | if (sem->id != __SEM_ID_LOCAL) { 185 | res = -1; 186 | errno = EINVAL; 187 | } else if (sem->__data.local.count) { 188 | sem->__data.local.count--; 189 | } else { 190 | res = -1; 191 | errno = EAGAIN; 192 | } 193 | 194 | pthread_mutex_unlock (&sem->__data.local.count_lock); 195 | 196 | return res; 197 | } 198 | 199 | /* http://www.opengroup.org/onlinepubs/007908799/xsh/sem_wait.html */ 200 | int 201 | compat_sem_wait(compat_sem_t *sem) 202 | { 203 | int res = 0; 204 | 205 | pthread_cleanup_push((void(*)(void*))&pthread_mutex_unlock, 206 | &sem->__data.local.count_lock); 207 | 208 | pthread_mutex_lock(&sem->__data.local.count_lock); 209 | 210 | if (sem->id != __SEM_ID_LOCAL) { 211 | errno = EINVAL; 212 | res = -1; 213 | } else { 214 | while (!sem->__data.local.count) { 215 | pthread_cond_wait(&sem->__data.local.count_cond, 216 | &sem->__data.local.count_lock); 217 | } 218 | if (sem->id != __SEM_ID_LOCAL) { 219 | res = -1; 220 | errno = EINVAL; 221 | } else { 222 | sem->__data.local.count--; 223 | } 224 | } 225 | 226 | pthread_cleanup_pop(1); 227 | 228 | return res; 229 | } 230 | -------------------------------------------------------------------------------- /compat/darwin_semaphore.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2000,02 Free Software Foundation, Inc. 2 | This file is part of the GNU C Library. 3 | Written by Gaël Le Mignot 4 | 5 | The GNU C Library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Library General Public License as 7 | published by the Free Software Foundation; either version 2 of the 8 | License, or (at your option) any later version. 9 | 10 | The GNU C Library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Library General Public License for more details. 14 | 15 | You should have received a copy of the GNU Library General Public 16 | License along with the GNU C Library; see the file COPYING.LIB. If not, 17 | write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 | Boston, MA 02111-1307, USA. */ 19 | 20 | // This implementation is based on libsem http://lists.debian.org/debian-devel/2004/08/msg00612.html 21 | 22 | #ifndef _SEMAPHORE_H_ 23 | #define _SEMAPHORE_H_ 24 | 25 | /* Caller must not include */ 26 | 27 | #include 28 | 29 | struct __local_sem_t 30 | { 31 | unsigned int count; 32 | pthread_mutex_t count_lock; 33 | pthread_cond_t count_cond; 34 | }; 35 | 36 | typedef struct compat_sem { 37 | unsigned int id; 38 | union { 39 | struct __local_sem_t local; 40 | } __data; 41 | } compat_sem_t; 42 | 43 | #define COMPAT_SEM_VALUE_MAX ((int32_t)32767) 44 | 45 | int compat_sem_init(compat_sem_t *sem, int pshared, unsigned int value); 46 | int compat_sem_destroy(compat_sem_t *sem); 47 | int compat_sem_getvalue(compat_sem_t *sem, unsigned int *value); 48 | int compat_sem_post(compat_sem_t *sem); 49 | int compat_sem_timedwait(compat_sem_t *sem, const struct timespec *abs_timeout); 50 | int compat_sem_trywait(compat_sem_t *sem); 51 | int compat_sem_wait(compat_sem_t *sem); 52 | 53 | 54 | /* Redefine semaphores. Caller must not include */ 55 | 56 | typedef compat_sem_t sem_t; 57 | 58 | #define sem_init(s, p, v) compat_sem_init(s, p, v) 59 | #define sem_destroy(s) compat_sem_destroy(s) 60 | #define sem_getvalue(s, v) compat_sem_getvalue(s, v) 61 | #define sem_post(s) compat_sem_post(s) 62 | #define sem_timedwait(s, t) compat_sem_timedwait(s, t) 63 | #define sem_trywait(s) compat_sem_trywait(s) 64 | #define sem_wait(s) compat_sem_wait(s) 65 | 66 | #define SEM_VALUE_MAX COMPAT_SEM_VALUE_MAX 67 | 68 | 69 | #endif /* semaphore.h */ 70 | -------------------------------------------------------------------------------- /compat/fuse_opt.c: -------------------------------------------------------------------------------- 1 | /* 2 | FUSE: Filesystem in Userspace 3 | Copyright (C) 2001-2006 Miklos Szeredi 4 | 5 | This program can be distributed under the terms of the GNU LGPL. 6 | See the file COPYING.LIB 7 | */ 8 | 9 | #include "fuse_opt.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | struct fuse_opt_context { 17 | void *data; 18 | const struct fuse_opt *opt; 19 | fuse_opt_proc_t proc; 20 | int argctr; 21 | int argc; 22 | char **argv; 23 | struct fuse_args outargs; 24 | char *opts; 25 | int nonopt; 26 | }; 27 | 28 | void fuse_opt_free_args(struct fuse_args *args) 29 | { 30 | if (args && args->argv && args->allocated) { 31 | int i; 32 | for (i = 0; i < args->argc; i++) 33 | free(args->argv[i]); 34 | free(args->argv); 35 | args->argv = NULL; 36 | args->allocated = 0; 37 | } 38 | } 39 | 40 | static int alloc_failed(void) 41 | { 42 | fprintf(stderr, "fuse: memory allocation failed\n"); 43 | return -1; 44 | } 45 | 46 | int fuse_opt_add_arg(struct fuse_args *args, const char *arg) 47 | { 48 | char **newargv; 49 | char *newarg; 50 | 51 | assert(!args->argv || args->allocated); 52 | 53 | newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *)); 54 | newarg = newargv ? strdup(arg) : NULL; 55 | if (!newargv || !newarg) 56 | return alloc_failed(); 57 | 58 | args->argv = newargv; 59 | args->allocated = 1; 60 | args->argv[args->argc++] = newarg; 61 | args->argv[args->argc] = NULL; 62 | return 0; 63 | } 64 | 65 | int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg) 66 | { 67 | assert(pos <= args->argc); 68 | if (fuse_opt_add_arg(args, arg) == -1) 69 | return -1; 70 | 71 | if (pos != args->argc - 1) { 72 | char *newarg = args->argv[args->argc - 1]; 73 | memmove(&args->argv[pos + 1], &args->argv[pos], 74 | sizeof(char *) * (args->argc - pos - 1)); 75 | args->argv[pos] = newarg; 76 | } 77 | return 0; 78 | } 79 | 80 | static int next_arg(struct fuse_opt_context *ctx, const char *opt) 81 | { 82 | if (ctx->argctr + 1 >= ctx->argc) { 83 | fprintf(stderr, "fuse: missing argument after `%s'\n", opt); 84 | return -1; 85 | } 86 | ctx->argctr++; 87 | return 0; 88 | } 89 | 90 | static int add_arg(struct fuse_opt_context *ctx, const char *arg) 91 | { 92 | return fuse_opt_add_arg(&ctx->outargs, arg); 93 | } 94 | 95 | int fuse_opt_add_opt(char **opts, const char *opt) 96 | { 97 | char *newopts; 98 | if (!*opts) 99 | newopts = strdup(opt); 100 | else { 101 | unsigned oldlen = strlen(*opts); 102 | newopts = realloc(*opts, oldlen + 1 + strlen(opt) + 1); 103 | if (newopts) { 104 | newopts[oldlen] = ','; 105 | strcpy(newopts + oldlen + 1, opt); 106 | } 107 | } 108 | if (!newopts) 109 | return alloc_failed(); 110 | 111 | *opts = newopts; 112 | return 0; 113 | } 114 | 115 | static int add_opt(struct fuse_opt_context *ctx, const char *opt) 116 | { 117 | return fuse_opt_add_opt(&ctx->opts, opt); 118 | } 119 | 120 | static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key, 121 | int iso) 122 | { 123 | if (key == FUSE_OPT_KEY_DISCARD) 124 | return 0; 125 | 126 | if (key != FUSE_OPT_KEY_KEEP && ctx->proc) { 127 | int res = ctx->proc(ctx->data, arg, key, &ctx->outargs); 128 | if (res == -1 || !res) 129 | return res; 130 | } 131 | if (iso) 132 | return add_opt(ctx, arg); 133 | else 134 | return add_arg(ctx, arg); 135 | } 136 | 137 | static int match_template(const char *t, const char *arg, unsigned *sepp) 138 | { 139 | int arglen = strlen(arg); 140 | const char *sep = strchr(t, '='); 141 | sep = sep ? sep : strchr(t, ' '); 142 | if (sep && (!sep[1] || sep[1] == '%')) { 143 | int tlen = sep - t; 144 | if (sep[0] == '=') 145 | tlen ++; 146 | if (arglen >= tlen && strncmp(arg, t, tlen) == 0) { 147 | *sepp = sep - t; 148 | return 1; 149 | } 150 | } 151 | if (strcmp(t, arg) == 0) { 152 | *sepp = 0; 153 | return 1; 154 | } 155 | return 0; 156 | } 157 | 158 | static const struct fuse_opt *find_opt(const struct fuse_opt *opt, 159 | const char *arg, unsigned *sepp) 160 | { 161 | for (; opt && opt->templ; opt++) 162 | if (match_template(opt->templ, arg, sepp)) 163 | return opt; 164 | return NULL; 165 | } 166 | 167 | int fuse_opt_match(const struct fuse_opt *opts, const char *opt) 168 | { 169 | unsigned dummy; 170 | return find_opt(opts, opt, &dummy) ? 1 : 0; 171 | } 172 | 173 | static int process_opt_param(void *var, const char *format, const char *param, 174 | const char *arg) 175 | { 176 | assert(format[0] == '%'); 177 | if (format[1] == 's') { 178 | char *copy = strdup(param); 179 | if (!copy) 180 | return alloc_failed(); 181 | 182 | *(char **) var = copy; 183 | } else { 184 | if (sscanf(param, format, var) != 1) { 185 | fprintf(stderr, "fuse: invalid parameter in option `%s'\n", arg); 186 | return -1; 187 | } 188 | } 189 | return 0; 190 | } 191 | 192 | static int process_opt(struct fuse_opt_context *ctx, 193 | const struct fuse_opt *opt, unsigned sep, 194 | const char *arg, int iso) 195 | { 196 | if (opt->offset == -1U) { 197 | if (call_proc(ctx, arg, opt->value, iso) == -1) 198 | return -1; 199 | } else { 200 | void *var = ctx->data + opt->offset; 201 | if (sep && opt->templ[sep + 1]) { 202 | const char *param = arg + sep; 203 | if (opt->templ[sep] == '=') 204 | param ++; 205 | if (process_opt_param(var, opt->templ + sep + 1, 206 | param, arg) == -1) 207 | return -1; 208 | } else 209 | *(int *)var = opt->value; 210 | } 211 | return 0; 212 | } 213 | 214 | static int process_opt_sep_arg(struct fuse_opt_context *ctx, 215 | const struct fuse_opt *opt, unsigned sep, 216 | const char *arg, int iso) 217 | { 218 | int res; 219 | char *newarg; 220 | char *param; 221 | 222 | if (next_arg(ctx, arg) == -1) 223 | return -1; 224 | 225 | param = ctx->argv[ctx->argctr]; 226 | newarg = malloc(sep + strlen(param) + 1); 227 | if (!newarg) 228 | return alloc_failed(); 229 | 230 | memcpy(newarg, arg, sep); 231 | strcpy(newarg + sep, param); 232 | res = process_opt(ctx, opt, sep, newarg, iso); 233 | free(newarg); 234 | 235 | return res; 236 | } 237 | 238 | static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso) 239 | { 240 | unsigned sep; 241 | const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep); 242 | if (opt) { 243 | for (; opt; opt = find_opt(opt + 1, arg, &sep)) { 244 | int res; 245 | if (sep && opt->templ[sep] == ' ' && !arg[sep]) 246 | res = process_opt_sep_arg(ctx, opt, sep, arg, iso); 247 | else 248 | res = process_opt(ctx, opt, sep, arg, iso); 249 | if (res == -1) 250 | return -1; 251 | } 252 | return 0; 253 | } else 254 | return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso); 255 | } 256 | 257 | static int process_real_option_group(struct fuse_opt_context *ctx, char *opts) 258 | { 259 | char *sep; 260 | 261 | do { 262 | int res; 263 | sep = strchr(opts, ','); 264 | if (sep) 265 | *sep = '\0'; 266 | res = process_gopt(ctx, opts, 1); 267 | if (res == -1) 268 | return -1; 269 | opts = sep + 1; 270 | } while (sep); 271 | 272 | return 0; 273 | } 274 | 275 | static int process_option_group(struct fuse_opt_context *ctx, const char *opts) 276 | { 277 | int res; 278 | char *copy; 279 | const char *sep = strchr(opts, ','); 280 | if (!sep) 281 | return process_gopt(ctx, opts, 1); 282 | 283 | copy = strdup(opts); 284 | if (!copy) { 285 | fprintf(stderr, "fuse: memory allocation failed\n"); 286 | return -1; 287 | } 288 | res = process_real_option_group(ctx, copy); 289 | free(copy); 290 | return res; 291 | } 292 | 293 | static int process_one(struct fuse_opt_context *ctx, const char *arg) 294 | { 295 | if (ctx->nonopt || arg[0] != '-') 296 | return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0); 297 | else if (arg[1] == 'o') { 298 | if (arg[2]) 299 | return process_option_group(ctx, arg + 2); 300 | else { 301 | if (next_arg(ctx, arg) == -1) 302 | return -1; 303 | 304 | return process_option_group(ctx, ctx->argv[ctx->argctr]); 305 | } 306 | } else if (arg[1] == '-' && !arg[2]) { 307 | if (add_arg(ctx, arg) == -1) 308 | return -1; 309 | ctx->nonopt = ctx->outargs.argc; 310 | return 0; 311 | } else 312 | return process_gopt(ctx, arg, 0); 313 | } 314 | 315 | static int opt_parse(struct fuse_opt_context *ctx) 316 | { 317 | if (ctx->argc) { 318 | if (add_arg(ctx, ctx->argv[0]) == -1) 319 | return -1; 320 | } 321 | 322 | for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++) 323 | if (process_one(ctx, ctx->argv[ctx->argctr]) == -1) 324 | return -1; 325 | 326 | if (ctx->opts) { 327 | if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 || 328 | fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1) 329 | return -1; 330 | } 331 | if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc) { 332 | free(ctx->outargs.argv[ctx->outargs.argc - 1]); 333 | ctx->outargs.argv[--ctx->outargs.argc] = NULL; 334 | } 335 | 336 | return 0; 337 | } 338 | 339 | int fuse_opt_parse(struct fuse_args *args, void *data, 340 | const struct fuse_opt opts[], fuse_opt_proc_t proc) 341 | { 342 | int res; 343 | struct fuse_opt_context ctx = { 344 | .data = data, 345 | .opt = opts, 346 | .proc = proc, 347 | }; 348 | 349 | if (!args || !args->argv || !args->argc) 350 | return 0; 351 | 352 | ctx.argc = args->argc; 353 | ctx.argv = args->argv; 354 | 355 | res = opt_parse(&ctx); 356 | if (res != -1) { 357 | struct fuse_args tmp = *args; 358 | *args = ctx.outargs; 359 | ctx.outargs = tmp; 360 | } 361 | free(ctx.opts); 362 | fuse_opt_free_args(&ctx.outargs); 363 | return res; 364 | } 365 | -------------------------------------------------------------------------------- /compat/fuse_opt.h: -------------------------------------------------------------------------------- 1 | /* 2 | FUSE: Filesystem in Userspace 3 | Copyright (C) 2001-2006 Miklos Szeredi 4 | 5 | This program can be distributed under the terms of the GNU GPL. 6 | See the file COPYING. 7 | */ 8 | 9 | #ifndef _FUSE_OPT_H_ 10 | #define _FUSE_OPT_H_ 11 | 12 | /* This file defines the option parsing interface of FUSE */ 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | /** 19 | * Option description 20 | * 21 | * This structure describes a single option, and and action associated 22 | * with it, in case it matches. 23 | * 24 | * More than one such match may occur, in which case the action for 25 | * each match is executed. 26 | * 27 | * There are three possible actions in case of a match: 28 | * 29 | * i) An integer (int or unsigned) variable determined by 'offset' is 30 | * set to 'value' 31 | * 32 | * ii) The processing function is called, with 'value' as the key 33 | * 34 | * iii) An integer (any) or string (char *) variable determined by 35 | * 'offset' is set to the value of an option parameter 36 | * 37 | * 'offset' should normally be either set to 38 | * 39 | * - 'offsetof(struct foo, member)' actions i) and iii) 40 | * 41 | * - -1 action ii) 42 | * 43 | * The 'offsetof()' macro is defined in the header. 44 | * 45 | * The template determines which options match, and also have an 46 | * effect on the action. Normally the action is either i) or ii), but 47 | * if a format is present in the template, then action iii) is 48 | * performed. 49 | * 50 | * The types of templates are: 51 | * 52 | * 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only 53 | * themselves. Invalid values are "--" and anything beginning 54 | * with "-o" 55 | * 56 | * 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or 57 | * the relevant option in a comma separated option list 58 | * 59 | * 3) "bar=", "--foo=", etc. These are variations of 1) and 2) 60 | * which have a parameter 61 | * 62 | * 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform 63 | * action iii). 64 | * 65 | * 5) "-x ", etc. Matches either "-xparam" or "-x param" as 66 | * two separate arguments 67 | * 68 | * 6) "-x %s", etc. Combination of 4) and 5) 69 | * 70 | * If the format is "%s", memory is allocated for the string unlike 71 | * with scanf(). 72 | */ 73 | struct fuse_opt { 74 | /** Matching template and optional parameter formatting */ 75 | const char *templ; 76 | 77 | /** 78 | * Offset of variable within 'data' parameter of fuse_opt_parse() 79 | * or -1 80 | */ 81 | unsigned long offset; 82 | 83 | /** 84 | * Value to set the variable to, or to be passed as 'key' to the 85 | * processing function. Ignored if template has a format 86 | */ 87 | int value; 88 | }; 89 | 90 | /** 91 | * Key option. In case of a match, the processing function will be 92 | * called with the specified key. 93 | */ 94 | #define FUSE_OPT_KEY(templ, key) { templ, -1U, key } 95 | 96 | /** 97 | * Last option. An array of 'struct fuse_opt' must end with a NULL 98 | * template value 99 | */ 100 | #define FUSE_OPT_END { .templ = NULL } 101 | 102 | /** 103 | * Argument list 104 | */ 105 | struct fuse_args { 106 | /** Argument count */ 107 | int argc; 108 | 109 | /** Argument vector. NULL terminated */ 110 | char **argv; 111 | 112 | /** Is 'argv' allocated? */ 113 | int allocated; 114 | }; 115 | 116 | /** 117 | * Initializer for 'struct fuse_args' 118 | */ 119 | #define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 } 120 | 121 | /** 122 | * Key value passed to the processing function if an option did not 123 | * match any template 124 | */ 125 | #define FUSE_OPT_KEY_OPT -1 126 | 127 | /** 128 | * Key value passed to the processing function for all non-options 129 | * 130 | * Non-options are the arguments beginning with a charater other than 131 | * '-' or all arguments after the special '--' option 132 | */ 133 | #define FUSE_OPT_KEY_NONOPT -2 134 | 135 | /** 136 | * Special key value for options to keep 137 | * 138 | * Argument is not passed to processing function, but behave as if the 139 | * processing function returned 1 140 | */ 141 | #define FUSE_OPT_KEY_KEEP -3 142 | 143 | /** 144 | * Special key value for options to discard 145 | * 146 | * Argument is not passed to processing function, but behave as if the 147 | * processing function returned zero 148 | */ 149 | #define FUSE_OPT_KEY_DISCARD -4 150 | 151 | /** 152 | * Processing function 153 | * 154 | * This function is called if 155 | * - option did not match any 'struct fuse_opt' 156 | * - argument is a non-option 157 | * - option did match and offset was set to -1 158 | * 159 | * The 'arg' parameter will always contain the whole argument or 160 | * option including the parameter if exists. A two-argument option 161 | * ("-x foo") is always converted to single arguemnt option of the 162 | * form "-xfoo" before this function is called. 163 | * 164 | * Options of the form '-ofoo' are passed to this function without the 165 | * '-o' prefix. 166 | * 167 | * The return value of this function determines whether this argument 168 | * is to be inserted into the output argument vector, or discarded. 169 | * 170 | * @param data is the user data passed to the fuse_opt_parse() function 171 | * @param arg is the whole argument or option 172 | * @param key determines why the processing function was called 173 | * @param outargs the current output argument list 174 | * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept 175 | */ 176 | typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, 177 | struct fuse_args *outargs); 178 | 179 | /** 180 | * Option parsing function 181 | * 182 | * If 'args' was returned from a previous call to fuse_opt_parse() or 183 | * it was constructed from 184 | * 185 | * A NULL 'args' is equivalent to an empty argument vector 186 | * 187 | * A NULL 'opts' is equivalent to an 'opts' array containing a single 188 | * end marker 189 | * 190 | * A NULL 'proc' is equivalent to a processing function always 191 | * returning '1' 192 | * 193 | * @param args is the input and output argument list 194 | * @param data is the user data 195 | * @param opts is the option description array 196 | * @param proc is the processing function 197 | * @return -1 on error, 0 on success 198 | */ 199 | int fuse_opt_parse(struct fuse_args *args, void *data, 200 | const struct fuse_opt opts[], fuse_opt_proc_t proc); 201 | 202 | /** 203 | * Add an option to a comma separated option list 204 | * 205 | * @param opts is a pointer to an option list, may point to a NULL value 206 | * @param opt is the option to add 207 | * @return -1 on allocation error, 0 on success 208 | */ 209 | int fuse_opt_add_opt(char **opts, const char *opt); 210 | 211 | /** 212 | * Add an argument to a NULL terminated argument vector 213 | * 214 | * @param args is the structure containing the current argument list 215 | * @param arg is the new argument to add 216 | * @return -1 on allocation error, 0 on success 217 | */ 218 | int fuse_opt_add_arg(struct fuse_args *args, const char *arg); 219 | 220 | /** 221 | * Add an argument at the specified position in a NULL terminated 222 | * argument vector 223 | * 224 | * Adds the argument to the N-th position. This is useful for adding 225 | * options at the beggining of the array which must not come after the 226 | * special '--' option. 227 | * 228 | * @param args is the structure containing the current argument list 229 | * @param pos is the position at which to add the argument 230 | * @param arg is the new argument to add 231 | * @return -1 on allocation error, 0 on success 232 | */ 233 | int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg); 234 | 235 | /** 236 | * Free the contents of argument list 237 | * 238 | * The structure itself is not freed 239 | * 240 | * @param args is the structure containing the argument list 241 | */ 242 | void fuse_opt_free_args(struct fuse_args *args); 243 | 244 | 245 | /** 246 | * Check if an option matches 247 | * 248 | * @param opts is the option description array 249 | * @param opt is the option to match 250 | * @return 1 if a match is found, 0 if not 251 | */ 252 | int fuse_opt_match(const struct fuse_opt opts[], const char *opt); 253 | 254 | #ifdef __cplusplus 255 | } 256 | #endif 257 | 258 | #endif /* _FUSE_OPT_H_ */ 259 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT(sshfs-fuse, 2.4.0) 2 | AM_INIT_AUTOMAKE 3 | AM_CONFIG_HEADER(config.h) 4 | 5 | AC_PROG_CC 6 | AM_PROG_CC_C_O 7 | CFLAGS="$CFLAGS -Wall -W -mmacosx-version-min=10.5" 8 | LIBS= 9 | AC_SEARCH_LIBS(dlsym, [dl]) 10 | sshnodelay_libs=$LIBS 11 | AC_SUBST(sshnodelay_libs) 12 | LIBS= 13 | 14 | AC_ARG_ENABLE(sshnodelay, 15 | [ --disable-sshnodelay Don't compile NODELAY workaround for ssh]) 16 | 17 | if test -z "$enable_sshnodelay"; then 18 | AC_MSG_CHECKING([OpenSSH version]) 19 | [eval `ssh -V 2>&1 | sed -n 's/^OpenSSH_\([1-9][0-9]*\)\.\([0-9][0-9]*\).*/ssh_major=\1 ssh_minor=\2/p'`] 20 | if test "x$ssh_major" != x -a "x$ssh_minor" != x; then 21 | if test $ssh_major -gt 4 -o \( $ssh_major = 4 -a $ssh_minor -ge 4 \); then 22 | AC_MSG_RESULT([$ssh_major.$ssh_minor >= 4.4, disabling NODELAY workaround]) 23 | enable_sshnodelay=no 24 | else 25 | AC_MSG_RESULT([$ssh_major.$ssh_minor < 4.4, enabling NODELAY workaround]) 26 | enable_sshnodelay=yes 27 | fi 28 | else 29 | AC_MSG_RESULT([not found]) 30 | fi 31 | fi 32 | 33 | if test "$enable_sshnodelay" = "yes"; then 34 | AC_DEFINE(SSH_NODELAY_WORKAROUND, 1, [Compile ssh NODELAY workaround]) 35 | fi 36 | 37 | AM_CONDITIONAL(SSH_NODELAY_SO, test "$enable_sshnodelay" = "yes") 38 | 39 | export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH 40 | PKG_CHECK_MODULES(SSHFS, [fuse >= 2.3 glib-2.0 gthread-2.0]) 41 | have_fuse_opt_parse=no 42 | oldlibs="$LIBS" 43 | LIBS="$LIBS $SSHFS_LIBS" 44 | AC_CHECK_FUNC([fuse_opt_parse], [have_fuse_opt_parse=yes]) 45 | LIBS="$oldlibs" 46 | if test "$have_fuse_opt_parse" = no; then 47 | CFLAGS="$CFLAGS -Icompat" 48 | fi 49 | AM_CONDITIONAL(FUSE_OPT_COMPAT, test "$have_fuse_opt_parse" = no) 50 | 51 | AC_CONFIG_FILES([Makefile]) 52 | AC_OUTPUT 53 | -------------------------------------------------------------------------------- /generate-faq.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | ( 4 | echo "See http://fuse.sourceforge.net/wiki/index.php/SshfsFaq for the latest" 5 | echo "version of this FAQ" 6 | echo "---" 7 | echo 8 | 9 | lynx -nolist -dump http://fuse.sourceforge.net/wiki/index.php/SshfsFaq \ 10 | | sed -e '1,12d' -e '/____/,$d' 11 | ) > FAQ.txt 12 | -------------------------------------------------------------------------------- /sshfs.1: -------------------------------------------------------------------------------- 1 | .TH SSHFS "1" "April 2008" "SSHFS version 2.0" "User Commands" 2 | .SH NAME 3 | SSHFS \- filesystem client based on ssh 4 | .SH SYNOPSIS 5 | .SS mounting 6 | .TP 7 | \fBsshfs\fP [\fIuser\fP@]\fBhost\fP:[\fIdir\fP] \fBmountpoint\fP [\fIoptions\fP] 8 | .SS unmounting 9 | .TP 10 | \fBfusermount -u mountpoint\fP 11 | .SH DESCRIPTION 12 | SSHFS (Secure SHell FileSystem) is a file system for Linux (and other 13 | operating systems with a FUSE implementation, such as Mac OS X or FreeBSD) 14 | capable of operating on files on a remote computer using just a secure 15 | shell login on the remote computer. On the local computer where the SSHFS 16 | is mounted, the implementation makes use of the FUSE (Filesystem in 17 | Userspace) kernel module. The practical effect of this is that the end user 18 | can seamlessly interact with remote files being securely served over SSH 19 | just as if they were local files on his/her computer. On the remote 20 | computer the SFTP subsystem of SSH is used. 21 | .PP 22 | If \fIhost\fP is a numeric IPv6 address, it needs to be enclosed in 23 | square brackets. 24 | .SH OPTIONS 25 | .SS "general options:" 26 | .TP 27 | \fB\-o\fR opt,[opt...] 28 | mount options 29 | .TP 30 | \fB\-h\fR \fB\-\-help\fR 31 | print help 32 | .TP 33 | \fB\-V\fR \fB\-\-version\fR 34 | print version 35 | .SS "SSHFS options:" 36 | .TP 37 | \fB\-p\fR PORT 38 | equivalent to '\-o port=PORT' 39 | .TP 40 | \fB\-C\fR 41 | equivalent to '\-o compression=yes' 42 | .TP 43 | \fB\-F\fR ssh_configfile 44 | specifies alternative ssh configuration file 45 | .TP 46 | \fB\-1\fR 47 | equivalent to '\-o ssh_protocol=1' 48 | .TP 49 | \fB\-o\fR reconnect 50 | reconnect to server 51 | .TP 52 | \fB\-o\fR delay_connect 53 | delay connection to server 54 | .TP 55 | \fB\-o\fR sshfs_sync 56 | synchronous writes 57 | .TP 58 | \fB\-o\fR no_readahead 59 | synchronous reads (no speculative readahead) 60 | .TP 61 | \fB\-o\fR sshfs_debug 62 | print some debugging information 63 | .TP 64 | \fB\-o\fR cache=BOOL 65 | enable caching {yes,no} (default: yes) 66 | .TP 67 | \fB\-o\fR cache_timeout=N 68 | sets timeout for caches in seconds (default: 20) 69 | .TP 70 | \fB\-o\fR cache_X_timeout=N 71 | sets timeout for {stat,dir,link} cache 72 | .TP 73 | \fB\-o\fR workaround=LIST 74 | colon separated list of workarounds 75 | .RS 8 76 | .TP 77 | none 78 | no workarounds enabled 79 | .TP 80 | all 81 | all workarounds enabled 82 | .TP 83 | [no]rename 84 | fix renaming to existing file (default: off) 85 | .TP 86 | [no]nodelaysrv 87 | set nodelay tcp flag in ssh (default: off) 88 | .TP 89 | [no]truncate 90 | fix truncate for old servers (default: off) 91 | .TP 92 | [no]buflimit 93 | fix buffer fillup bug in server (default: on) 94 | .RE 95 | .TP 96 | \fB\-o\fR idmap=TYPE 97 | user/group ID mapping, possible types are: 98 | .RS 8 99 | .TP 100 | none 101 | no translation of the ID space (default) 102 | .TP 103 | user 104 | only translate UID of connecting user 105 | .TP 106 | file 107 | translate UIDs/GIDs based upon the contents of \fBuidfile \fR and 108 | \fBgidfile\fR 109 | .RE 110 | .TP 111 | \fB\-o\fR uidfile=FILE 112 | file containing username:uid mappings for \fBidmap=file\fR 113 | .RE 114 | .TP 115 | \fB\-o\fR gidfile=FILE 116 | file containing groupname:gid mappings for \fBidmap=file\fR 117 | .RE 118 | .TP 119 | \fB\-o\fR nomap=TYPE 120 | with idmap=file, how to handle missing mappings 121 | .RS 8 122 | .TP 123 | ignore 124 | don't do any re-mapping 125 | .TP 126 | error 127 | return an error (default) 128 | .RE 129 | .TP 130 | \fB\-o\fR ssh_command=CMD 131 | execute CMD instead of 'ssh' 132 | .TP 133 | \fB\-o\fR ssh_protocol=N 134 | ssh protocol to use (default: 2) 135 | .TP 136 | \fB\-o\fR sftp_server=SERV 137 | path to sftp server or subsystem (default: sftp) 138 | .TP 139 | \fB\-o\fR directport=PORT 140 | directly connect to PORT bypassing ssh 141 | \fB\-o\fR slave 142 | communicate over stdin and stdout bypassing network 143 | .TP 144 | \fB\-o\fR transform_symlinks 145 | transform absolute symlinks to relative 146 | .TP 147 | \fB\-o\fR follow_symlinks 148 | follow symlinks on the server 149 | .TP 150 | \fB\-o\fR no_check_root 151 | don't check for existence of 'dir' on server 152 | .TP 153 | \fB\-o\fR password_stdin 154 | read password from stdin (only for pam_mount!) 155 | .TP 156 | \fB\-o\fR SSHOPT=VAL 157 | ssh options (see man ssh_config) 158 | .SS "FUSE options:" 159 | .TP 160 | \fB\-d\fR \fB\-o\fR debug 161 | enable debug output (implies \fB\-f\fR) 162 | .TP 163 | \fB\-f\fR 164 | foreground operation 165 | .TP 166 | \fB\-s\fR 167 | disable multi\-threaded operation 168 | .TP 169 | \fB\-o\fR allow_other 170 | allow access to other users 171 | .TP 172 | \fB\-o\fR allow_root 173 | allow access to root 174 | .TP 175 | \fB\-o\fR nonempty 176 | allow mounts over non\-empty file/dir 177 | .HP 178 | \fB\-o\fR default_permissions 179 | enable permission checking by kernel 180 | .TP 181 | \fB\-o\fR fsname=NAME 182 | set filesystem name 183 | .TP 184 | \fB\-o\fR subtype=NAME 185 | set filesystem type 186 | .TP 187 | \fB\-o\fR large_read 188 | issue large read requests (2.4 only) 189 | .TP 190 | \fB\-o\fR max_read=N 191 | set maximum size of read requests 192 | .TP 193 | \fB\-o\fR hard_remove 194 | immediate removal (don't hide files) 195 | .TP 196 | \fB\-o\fR use_ino 197 | let filesystem set inode numbers 198 | .TP 199 | \fB\-o\fR readdir_ino 200 | try to fill in d_ino in readdir 201 | .TP 202 | \fB\-o\fR direct_io 203 | use direct I/O 204 | .TP 205 | \fB\-o\fR kernel_cache 206 | cache files in kernel 207 | .TP 208 | \fB\-o\fR [no]auto_cache 209 | enable caching based on modification times 210 | .TP 211 | \fB\-o\fR umask=M 212 | set file permissions (octal) 213 | .TP 214 | \fB\-o\fR uid=N 215 | set file owner 216 | .TP 217 | \fB\-o\fR gid=N 218 | set file group 219 | .TP 220 | \fB\-o\fR entry_timeout=T 221 | cache timeout for names (1.0s) 222 | .TP 223 | \fB\-o\fR negative_timeout=T 224 | cache timeout for deleted names (0.0s) 225 | .TP 226 | \fB\-o\fR attr_timeout=T 227 | cache timeout for attributes (1.0s) 228 | .TP 229 | \fB\-o\fR ac_attr_timeout=T 230 | auto cache timeout for attributes (attr_timeout) 231 | .TP 232 | \fB\-o\fR intr 233 | allow requests to be interrupted 234 | .TP 235 | \fB\-o\fR intr_signal=NUM 236 | signal to send on interrupt (10) 237 | .TP 238 | \fB\-o\fR modules=M1[:M2...] 239 | names of modules to push onto filesystem stack 240 | .TP 241 | \fB\-o\fR max_write=N 242 | set maximum size of write requests 243 | .TP 244 | \fB\-o\fR max_readahead=N 245 | set maximum readahead 246 | .TP 247 | \fB\-o\fR async_read 248 | perform reads asynchronously (default) 249 | .TP 250 | \fB\-o\fR sync_read 251 | perform reads synchronously 252 | .SS "Module options:" 253 | .TP 254 | [subdir] 255 | .TP 256 | \fB\-o\fR subdir=DIR 257 | prepend this directory to all paths (mandatory) 258 | .TP 259 | \fB\-o\fR [no]rellinksa 260 | transform absolute symlinks to relative 261 | .TP 262 | [iconv] 263 | .TP 264 | \fB\-o\fR from_code=CHARSET 265 | original encoding of file names (default: UTF-8) 266 | .TP 267 | \fB\-o\fR to_code=CHARSET 268 | new encoding of the file names (default: ISO-8859-2) 269 | .PD 270 | .SH "AUTHORS" 271 | .LP 272 | SSHFS has been written by Miklos Szeredi . 273 | .LP 274 | This man page was written by Bartosz Fenski for the 275 | Debian GNU/Linux distribution (but it may be used by others). 276 | 277 | 278 | -------------------------------------------------------------------------------- /sshfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | SSH file system 3 | Copyright (C) 2004 Miklos Szeredi 4 | 5 | This program can be distributed under the terms of the GNU GPL. 6 | See the file COPYING. 7 | */ 8 | 9 | #define _GNU_SOURCE /* avoid implicit declaration of *pt* functions */ 10 | #include "config.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #ifdef __APPLE__ 24 | #include 25 | #else 26 | #include 27 | #endif 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #ifdef __APPLE__ 46 | #include 47 | #include 48 | #endif 49 | 50 | #include "cache.h" 51 | 52 | #ifndef MAP_LOCKED 53 | #define MAP_LOCKED 0 54 | #endif 55 | 56 | #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) 57 | #define MAP_ANONYMOUS MAP_ANON 58 | #endif 59 | 60 | 61 | #define SSH_FXP_INIT 1 62 | #define SSH_FXP_VERSION 2 63 | #define SSH_FXP_OPEN 3 64 | #define SSH_FXP_CLOSE 4 65 | #define SSH_FXP_READ 5 66 | #define SSH_FXP_WRITE 6 67 | #define SSH_FXP_LSTAT 7 68 | #define SSH_FXP_FSTAT 8 69 | #define SSH_FXP_SETSTAT 9 70 | #define SSH_FXP_FSETSTAT 10 71 | #define SSH_FXP_OPENDIR 11 72 | #define SSH_FXP_READDIR 12 73 | #define SSH_FXP_REMOVE 13 74 | #define SSH_FXP_MKDIR 14 75 | #define SSH_FXP_RMDIR 15 76 | #define SSH_FXP_REALPATH 16 77 | #define SSH_FXP_STAT 17 78 | #define SSH_FXP_RENAME 18 79 | #define SSH_FXP_READLINK 19 80 | #define SSH_FXP_SYMLINK 20 81 | #define SSH_FXP_STATUS 101 82 | #define SSH_FXP_HANDLE 102 83 | #define SSH_FXP_DATA 103 84 | #define SSH_FXP_NAME 104 85 | #define SSH_FXP_ATTRS 105 86 | #define SSH_FXP_EXTENDED 200 87 | #define SSH_FXP_EXTENDED_REPLY 201 88 | 89 | #define SSH_FILEXFER_ATTR_SIZE 0x00000001 90 | #define SSH_FILEXFER_ATTR_UIDGID 0x00000002 91 | #define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 92 | #define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 93 | #define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 94 | 95 | #define SSH_FX_OK 0 96 | #define SSH_FX_EOF 1 97 | #define SSH_FX_NO_SUCH_FILE 2 98 | #define SSH_FX_PERMISSION_DENIED 3 99 | #define SSH_FX_FAILURE 4 100 | #define SSH_FX_BAD_MESSAGE 5 101 | #define SSH_FX_NO_CONNECTION 6 102 | #define SSH_FX_CONNECTION_LOST 7 103 | #define SSH_FX_OP_UNSUPPORTED 8 104 | 105 | #define SSH_FXF_READ 0x00000001 106 | #define SSH_FXF_WRITE 0x00000002 107 | #define SSH_FXF_APPEND 0x00000004 108 | #define SSH_FXF_CREAT 0x00000008 109 | #define SSH_FXF_TRUNC 0x00000010 110 | #define SSH_FXF_EXCL 0x00000020 111 | 112 | /* statvfs@openssh.com f_flag flags */ 113 | #define SSH2_FXE_STATVFS_ST_RDONLY 0x00000001 114 | #define SSH2_FXE_STATVFS_ST_NOSUID 0x00000002 115 | 116 | #define SFTP_EXT_POSIX_RENAME "posix-rename@openssh.com" 117 | #define SFTP_EXT_STATVFS "statvfs@openssh.com" 118 | #define SFTP_EXT_HARDLINK "hardlink@openssh.com" 119 | 120 | #define PROTO_VERSION 3 121 | 122 | #define MY_EOF 1 123 | 124 | #define MAX_REPLY_LEN (1 << 17) 125 | 126 | #define RENAME_TEMP_CHARS 8 127 | 128 | #define SFTP_SERVER_PATH "/usr/lib/sftp-server" 129 | 130 | #define SSHNODELAY_SO "sshnodelay.so" 131 | 132 | #ifdef __APPLE__ 133 | 134 | #ifndef LIBDIR 135 | #define LIBDIR "/usr/local/lib" 136 | #endif 137 | 138 | static char sshfs_program_path[PATH_MAX] = { 0 }; 139 | 140 | #endif 141 | 142 | struct buffer { 143 | uint8_t *p; 144 | size_t len; 145 | size_t size; 146 | }; 147 | 148 | struct list_head { 149 | struct list_head *prev; 150 | struct list_head *next; 151 | }; 152 | 153 | struct request; 154 | typedef void (*request_func)(struct request *); 155 | 156 | struct request { 157 | unsigned int want_reply; 158 | sem_t ready; 159 | uint8_t reply_type; 160 | int replied; 161 | int error; 162 | struct buffer reply; 163 | struct timeval start; 164 | void *data; 165 | request_func end_func; 166 | size_t len; 167 | struct list_head list; 168 | }; 169 | 170 | struct sshfs_io { 171 | int num_reqs; 172 | pthread_cond_t finished; 173 | int error; 174 | }; 175 | 176 | struct read_req { 177 | struct sshfs_io *sio; 178 | struct list_head list; 179 | struct buffer data; 180 | size_t size; 181 | ssize_t res; 182 | }; 183 | 184 | struct read_chunk { 185 | off_t offset; 186 | size_t size; 187 | int refs; 188 | long modifver; 189 | struct list_head reqs; 190 | struct sshfs_io sio; 191 | }; 192 | 193 | struct sshfs_file { 194 | struct buffer handle; 195 | struct list_head write_reqs; 196 | pthread_cond_t write_finished; 197 | int write_error; 198 | struct read_chunk *readahead; 199 | off_t next_pos; 200 | int is_seq; 201 | int connver; 202 | int modifver; 203 | int refs; 204 | #ifdef __APPLE__ 205 | pthread_mutex_t file_lock; 206 | #endif 207 | }; 208 | 209 | struct sshfs { 210 | char *directport; 211 | char *ssh_command; 212 | char *sftp_server; 213 | struct fuse_args ssh_args; 214 | char *workarounds; 215 | int rename_workaround; 216 | int nodelay_workaround; 217 | int nodelaysrv_workaround; 218 | int truncate_workaround; 219 | int buflimit_workaround; 220 | int fstat_workaround; 221 | int transform_symlinks; 222 | int follow_symlinks; 223 | int no_check_root; 224 | int detect_uid; 225 | int idmap; 226 | int nomap; 227 | char *uid_file; 228 | char *gid_file; 229 | GHashTable *uid_map; 230 | GHashTable *gid_map; 231 | GHashTable *r_uid_map; 232 | GHashTable *r_gid_map; 233 | unsigned max_read; 234 | unsigned max_write; 235 | unsigned ssh_ver; 236 | int sync_write; 237 | int sync_read; 238 | int debug; 239 | int foreground; 240 | int reconnect; 241 | int delay_connect; 242 | int slave; 243 | char *host; 244 | char *base_path; 245 | GHashTable *reqtab; 246 | pthread_mutex_t lock; 247 | pthread_mutex_t lock_write; 248 | int processing_thread_started; 249 | unsigned int randseed; 250 | int rfd; 251 | int wfd; 252 | int ptyfd; 253 | int ptyslavefd; 254 | int connver; 255 | int server_version; 256 | unsigned remote_uid; 257 | unsigned local_uid; 258 | #ifdef __APPLE__ 259 | unsigned remote_gid; 260 | unsigned local_gid; 261 | #endif 262 | int remote_uid_detected; 263 | unsigned blksize; 264 | char *progname; 265 | long modifver; 266 | unsigned outstanding_len; 267 | unsigned max_outstanding_len; 268 | pthread_cond_t outstanding_cond; 269 | int password_stdin; 270 | char *password; 271 | int ext_posix_rename; 272 | int ext_statvfs; 273 | int ext_hardlink; 274 | mode_t mnt_mode; 275 | 276 | /* statistics */ 277 | uint64_t bytes_sent; 278 | uint64_t bytes_received; 279 | uint64_t num_sent; 280 | uint64_t num_received; 281 | unsigned int min_rtt; 282 | unsigned int max_rtt; 283 | uint64_t total_rtt; 284 | unsigned int num_connect; 285 | }; 286 | 287 | static struct sshfs sshfs; 288 | 289 | static const char *ssh_opts[] = { 290 | "AddressFamily", 291 | "BatchMode", 292 | "BindAddress", 293 | "ChallengeResponseAuthentication", 294 | "CheckHostIP", 295 | "Cipher", 296 | "Ciphers", 297 | "Compression", 298 | "CompressionLevel", 299 | "ConnectionAttempts", 300 | "ConnectTimeout", 301 | "ControlMaster", 302 | "ControlPath", 303 | "GlobalKnownHostsFile", 304 | "GSSAPIAuthentication", 305 | "GSSAPIDelegateCredentials", 306 | "HostbasedAuthentication", 307 | "HostKeyAlgorithms", 308 | "HostKeyAlias", 309 | "HostName", 310 | "IdentitiesOnly", 311 | "IdentityFile", 312 | "KbdInteractiveAuthentication", 313 | "KbdInteractiveDevices", 314 | "LocalCommand", 315 | "LogLevel", 316 | "MACs", 317 | "NoHostAuthenticationForLocalhost", 318 | "NumberOfPasswordPrompts", 319 | "PasswordAuthentication", 320 | "Port", 321 | "PreferredAuthentications", 322 | "ProxyCommand", 323 | "PubkeyAuthentication", 324 | "RekeyLimit", 325 | "RhostsRSAAuthentication", 326 | "RSAAuthentication", 327 | "ServerAliveCountMax", 328 | "ServerAliveInterval", 329 | "SmartcardDevice", 330 | "StrictHostKeyChecking", 331 | "TCPKeepAlive", 332 | "UsePrivilegedPort", 333 | "UserKnownHostsFile", 334 | "VerifyHostKeyDNS", 335 | NULL, 336 | }; 337 | 338 | enum { 339 | KEY_PORT, 340 | KEY_COMPRESS, 341 | KEY_HELP, 342 | KEY_VERSION, 343 | KEY_FOREGROUND, 344 | KEY_CONFIGFILE, 345 | }; 346 | 347 | enum { 348 | IDMAP_NONE, 349 | IDMAP_USER, 350 | IDMAP_FILE, 351 | }; 352 | 353 | enum { 354 | NOMAP_IGNORE, 355 | NOMAP_ERROR, 356 | }; 357 | 358 | #define SSHFS_OPT(t, p, v) { t, offsetof(struct sshfs, p), v } 359 | 360 | static struct fuse_opt sshfs_opts[] = { 361 | SSHFS_OPT("directport=%s", directport, 0), 362 | SSHFS_OPT("ssh_command=%s", ssh_command, 0), 363 | SSHFS_OPT("sftp_server=%s", sftp_server, 0), 364 | SSHFS_OPT("max_read=%u", max_read, 0), 365 | SSHFS_OPT("max_write=%u", max_write, 0), 366 | SSHFS_OPT("ssh_protocol=%u", ssh_ver, 0), 367 | SSHFS_OPT("-1", ssh_ver, 1), 368 | SSHFS_OPT("workaround=%s", workarounds, 0), 369 | SSHFS_OPT("idmap=none", idmap, IDMAP_NONE), 370 | SSHFS_OPT("idmap=user", idmap, IDMAP_USER), 371 | SSHFS_OPT("idmap=file", idmap, IDMAP_FILE), 372 | SSHFS_OPT("uidfile=%s", uid_file, 0), 373 | SSHFS_OPT("gidfile=%s", gid_file, 0), 374 | SSHFS_OPT("nomap=ignore", nomap, NOMAP_IGNORE), 375 | SSHFS_OPT("nomap=error", nomap, NOMAP_ERROR), 376 | SSHFS_OPT("sshfs_sync", sync_write, 1), 377 | SSHFS_OPT("no_readahead", sync_read, 1), 378 | SSHFS_OPT("sshfs_debug", debug, 1), 379 | SSHFS_OPT("reconnect", reconnect, 1), 380 | SSHFS_OPT("transform_symlinks", transform_symlinks, 1), 381 | SSHFS_OPT("follow_symlinks", follow_symlinks, 1), 382 | SSHFS_OPT("no_check_root", no_check_root, 1), 383 | SSHFS_OPT("password_stdin", password_stdin, 1), 384 | SSHFS_OPT("delay_connect", delay_connect, 1), 385 | SSHFS_OPT("slave", slave, 1), 386 | 387 | FUSE_OPT_KEY("-p ", KEY_PORT), 388 | FUSE_OPT_KEY("-C", KEY_COMPRESS), 389 | FUSE_OPT_KEY("-V", KEY_VERSION), 390 | FUSE_OPT_KEY("--version", KEY_VERSION), 391 | FUSE_OPT_KEY("-h", KEY_HELP), 392 | FUSE_OPT_KEY("--help", KEY_HELP), 393 | FUSE_OPT_KEY("debug", KEY_FOREGROUND), 394 | FUSE_OPT_KEY("-d", KEY_FOREGROUND), 395 | FUSE_OPT_KEY("-f", KEY_FOREGROUND), 396 | FUSE_OPT_KEY("-F ", KEY_CONFIGFILE), 397 | FUSE_OPT_END 398 | }; 399 | 400 | static struct fuse_opt workaround_opts[] = { 401 | SSHFS_OPT("none", rename_workaround, 0), 402 | SSHFS_OPT("none", nodelay_workaround, 0), 403 | SSHFS_OPT("none", nodelaysrv_workaround, 0), 404 | SSHFS_OPT("none", truncate_workaround, 0), 405 | SSHFS_OPT("none", buflimit_workaround, 0), 406 | SSHFS_OPT("none", fstat_workaround, 0), 407 | SSHFS_OPT("all", rename_workaround, 1), 408 | SSHFS_OPT("all", nodelay_workaround, 1), 409 | SSHFS_OPT("all", nodelaysrv_workaround, 1), 410 | SSHFS_OPT("all", truncate_workaround, 1), 411 | SSHFS_OPT("all", buflimit_workaround, 1), 412 | SSHFS_OPT("all", fstat_workaround, 1), 413 | SSHFS_OPT("rename", rename_workaround, 1), 414 | SSHFS_OPT("norename", rename_workaround, 0), 415 | SSHFS_OPT("nodelay", nodelay_workaround, 1), 416 | SSHFS_OPT("nonodelay", nodelay_workaround, 0), 417 | SSHFS_OPT("nodelaysrv", nodelaysrv_workaround, 1), 418 | SSHFS_OPT("nonodelaysrv", nodelaysrv_workaround, 0), 419 | SSHFS_OPT("truncate", truncate_workaround, 1), 420 | SSHFS_OPT("notruncate", truncate_workaround, 0), 421 | SSHFS_OPT("buflimit", buflimit_workaround, 1), 422 | SSHFS_OPT("nobuflimit", buflimit_workaround, 0), 423 | SSHFS_OPT("fstat", fstat_workaround, 1), 424 | SSHFS_OPT("nofstat", fstat_workaround, 0), 425 | FUSE_OPT_END 426 | }; 427 | 428 | #define DEBUG(format, args...) \ 429 | do { if (sshfs.debug) fprintf(stderr, format, args); } while(0) 430 | 431 | static const char *type_name(uint8_t type) 432 | { 433 | switch(type) { 434 | case SSH_FXP_INIT: return "INIT"; 435 | case SSH_FXP_VERSION: return "VERSION"; 436 | case SSH_FXP_OPEN: return "OPEN"; 437 | case SSH_FXP_CLOSE: return "CLOSE"; 438 | case SSH_FXP_READ: return "READ"; 439 | case SSH_FXP_WRITE: return "WRITE"; 440 | case SSH_FXP_LSTAT: return "LSTAT"; 441 | case SSH_FXP_FSTAT: return "FSTAT"; 442 | case SSH_FXP_SETSTAT: return "SETSTAT"; 443 | case SSH_FXP_FSETSTAT: return "FSETSTAT"; 444 | case SSH_FXP_OPENDIR: return "OPENDIR"; 445 | case SSH_FXP_READDIR: return "READDIR"; 446 | case SSH_FXP_REMOVE: return "REMOVE"; 447 | case SSH_FXP_MKDIR: return "MKDIR"; 448 | case SSH_FXP_RMDIR: return "RMDIR"; 449 | case SSH_FXP_REALPATH: return "REALPATH"; 450 | case SSH_FXP_STAT: return "STAT"; 451 | case SSH_FXP_RENAME: return "RENAME"; 452 | case SSH_FXP_READLINK: return "READLINK"; 453 | case SSH_FXP_SYMLINK: return "SYMLINK"; 454 | case SSH_FXP_STATUS: return "STATUS"; 455 | case SSH_FXP_HANDLE: return "HANDLE"; 456 | case SSH_FXP_DATA: return "DATA"; 457 | case SSH_FXP_NAME: return "NAME"; 458 | case SSH_FXP_ATTRS: return "ATTRS"; 459 | case SSH_FXP_EXTENDED: return "EXTENDED"; 460 | case SSH_FXP_EXTENDED_REPLY: return "EXTENDED_REPLY"; 461 | default: return "???"; 462 | } 463 | } 464 | 465 | #define container_of(ptr, type, member) ({ \ 466 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 467 | (type *)( (char *)__mptr - offsetof(type,member) );}) 468 | 469 | #define list_entry(ptr, type, member) \ 470 | container_of(ptr, type, member) 471 | 472 | static void list_init(struct list_head *head) 473 | { 474 | head->next = head; 475 | head->prev = head; 476 | } 477 | 478 | static void list_add(struct list_head *new, struct list_head *head) 479 | { 480 | struct list_head *prev = head; 481 | struct list_head *next = head->next; 482 | next->prev = new; 483 | new->next = next; 484 | new->prev = prev; 485 | prev->next = new; 486 | } 487 | 488 | static void list_del(struct list_head *entry) 489 | { 490 | struct list_head *prev = entry->prev; 491 | struct list_head *next = entry->next; 492 | next->prev = prev; 493 | prev->next = next; 494 | 495 | } 496 | 497 | static int list_empty(const struct list_head *head) 498 | { 499 | return head->next == head; 500 | } 501 | 502 | /* given a pointer to the uid/gid, and the mapping table, remap the 503 | * uid/gid, if necessary */ 504 | static inline int translate_id(uint32_t *id, GHashTable *map) 505 | { 506 | gpointer id_p; 507 | if (g_hash_table_lookup_extended(map, GUINT_TO_POINTER(*id), NULL, &id_p)) { 508 | *id = GPOINTER_TO_UINT(id_p); 509 | return 0; 510 | } 511 | switch (sshfs.nomap) { 512 | case NOMAP_ERROR: return -1; 513 | case NOMAP_IGNORE: return 0; 514 | default: 515 | fprintf(stderr, "internal error\n"); 516 | abort(); 517 | } 518 | } 519 | 520 | static inline void buf_init(struct buffer *buf, size_t size) 521 | { 522 | if (size) { 523 | buf->p = (uint8_t *) malloc(size); 524 | if (!buf->p) { 525 | fprintf(stderr, "sshfs: memory allocation failed\n"); 526 | abort(); 527 | } 528 | } else 529 | buf->p = NULL; 530 | buf->len = 0; 531 | buf->size = size; 532 | } 533 | 534 | static inline void buf_free(struct buffer *buf) 535 | { 536 | free(buf->p); 537 | } 538 | 539 | static inline void buf_finish(struct buffer *buf) 540 | { 541 | buf->len = buf->size; 542 | } 543 | 544 | static inline void buf_clear(struct buffer *buf) 545 | { 546 | buf_free(buf); 547 | buf_init(buf, 0); 548 | } 549 | 550 | static void buf_resize(struct buffer *buf, size_t len) 551 | { 552 | buf->size = (buf->len + len + 63) & ~31; 553 | buf->p = (uint8_t *) realloc(buf->p, buf->size); 554 | if (!buf->p) { 555 | fprintf(stderr, "sshfs: memory allocation failed\n"); 556 | abort(); 557 | } 558 | } 559 | 560 | static inline void buf_check_add(struct buffer *buf, size_t len) 561 | { 562 | if (buf->len + len > buf->size) 563 | buf_resize(buf, len); 564 | } 565 | 566 | #define _buf_add_mem(b, d, l) \ 567 | buf_check_add(b, l); \ 568 | memcpy(b->p + b->len, d, l); \ 569 | b->len += l; 570 | 571 | 572 | static inline void buf_add_mem(struct buffer *buf, const void *data, 573 | size_t len) 574 | { 575 | _buf_add_mem(buf, data, len); 576 | } 577 | 578 | static inline void buf_add_buf(struct buffer *buf, const struct buffer *bufa) 579 | { 580 | _buf_add_mem(buf, bufa->p, bufa->len); 581 | } 582 | 583 | static inline void buf_add_uint8(struct buffer *buf, uint8_t val) 584 | { 585 | _buf_add_mem(buf, &val, 1); 586 | } 587 | 588 | static inline void buf_add_uint32(struct buffer *buf, uint32_t val) 589 | { 590 | uint32_t nval = htonl(val); 591 | _buf_add_mem(buf, &nval, 4); 592 | } 593 | 594 | static inline void buf_add_uint64(struct buffer *buf, uint64_t val) 595 | { 596 | buf_add_uint32(buf, val >> 32); 597 | buf_add_uint32(buf, val & 0xffffffff); 598 | } 599 | 600 | static inline void buf_add_data(struct buffer *buf, const struct buffer *data) 601 | { 602 | buf_add_uint32(buf, data->len); 603 | buf_add_mem(buf, data->p, data->len); 604 | } 605 | 606 | static inline void buf_add_string(struct buffer *buf, const char *str) 607 | { 608 | struct buffer data; 609 | data.p = (uint8_t *) str; 610 | data.len = strlen(str); 611 | buf_add_data(buf, &data); 612 | } 613 | 614 | static inline void buf_add_path(struct buffer *buf, const char *path) 615 | { 616 | char *realpath; 617 | 618 | if (sshfs.base_path[0]) { 619 | if (path[1]) { 620 | if (sshfs.base_path[strlen(sshfs.base_path)-1] != '/') { 621 | realpath = g_strdup_printf("%s/%s", 622 | sshfs.base_path, 623 | path + 1); 624 | } else { 625 | realpath = g_strdup_printf("%s%s", 626 | sshfs.base_path, 627 | path + 1); 628 | } 629 | } else { 630 | realpath = g_strdup(sshfs.base_path); 631 | } 632 | } else { 633 | if (path[1]) 634 | realpath = g_strdup(path + 1); 635 | else 636 | realpath = g_strdup("."); 637 | } 638 | buf_add_string(buf, realpath); 639 | g_free(realpath); 640 | } 641 | 642 | static int buf_check_get(struct buffer *buf, size_t len) 643 | { 644 | if (buf->len + len > buf->size) { 645 | fprintf(stderr, "buffer too short\n"); 646 | return -1; 647 | } else 648 | return 0; 649 | } 650 | 651 | static inline int buf_get_mem(struct buffer *buf, void *data, size_t len) 652 | { 653 | if (buf_check_get(buf, len) == -1) 654 | return -1; 655 | memcpy(data, buf->p + buf->len, len); 656 | buf->len += len; 657 | return 0; 658 | } 659 | 660 | static inline int buf_get_uint8(struct buffer *buf, uint8_t *val) 661 | { 662 | return buf_get_mem(buf, val, 1); 663 | } 664 | 665 | static inline int buf_get_uint32(struct buffer *buf, uint32_t *val) 666 | { 667 | uint32_t nval; 668 | if (buf_get_mem(buf, &nval, 4) == -1) 669 | return -1; 670 | *val = ntohl(nval); 671 | return 0; 672 | } 673 | 674 | static inline int buf_get_uint64(struct buffer *buf, uint64_t *val) 675 | { 676 | uint32_t val1; 677 | uint32_t val2; 678 | if (buf_get_uint32(buf, &val1) == -1 || 679 | buf_get_uint32(buf, &val2) == -1) { 680 | return -1; 681 | } 682 | *val = ((uint64_t) val1 << 32) + val2; 683 | return 0; 684 | } 685 | 686 | static inline int buf_get_data(struct buffer *buf, struct buffer *data) 687 | { 688 | uint32_t len; 689 | if (buf_get_uint32(buf, &len) == -1 || len > buf->size - buf->len) 690 | return -1; 691 | buf_init(data, len + 1); 692 | data->size = len; 693 | if (buf_get_mem(buf, data->p, data->size) == -1) { 694 | buf_free(data); 695 | return -1; 696 | } 697 | return 0; 698 | } 699 | 700 | static inline int buf_get_string(struct buffer *buf, char **str) 701 | { 702 | struct buffer data; 703 | if (buf_get_data(buf, &data) == -1) 704 | return -1; 705 | data.p[data.size] = '\0'; 706 | *str = (char *) data.p; 707 | return 0; 708 | } 709 | 710 | static int buf_get_attrs(struct buffer *buf, struct stat *stbuf, int *flagsp) 711 | { 712 | uint32_t flags; 713 | uint64_t size = 0; 714 | uint32_t uid = 0; 715 | uint32_t gid = 0; 716 | uint32_t atime = 0; 717 | uint32_t mtime = 0; 718 | uint32_t mode = S_IFREG | 0777; 719 | 720 | if (buf_get_uint32(buf, &flags) == -1) 721 | return -EIO; 722 | if (flagsp) 723 | *flagsp = flags; 724 | if ((flags & SSH_FILEXFER_ATTR_SIZE) && 725 | buf_get_uint64(buf, &size) == -1) 726 | return -EIO; 727 | if ((flags & SSH_FILEXFER_ATTR_UIDGID) && 728 | (buf_get_uint32(buf, &uid) == -1 || 729 | buf_get_uint32(buf, &gid) == -1)) 730 | return -EIO; 731 | if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) && 732 | buf_get_uint32(buf, &mode) == -1) 733 | return -EIO; 734 | if ((flags & SSH_FILEXFER_ATTR_ACMODTIME)) { 735 | if (buf_get_uint32(buf, &atime) == -1 || 736 | buf_get_uint32(buf, &mtime) == -1) 737 | return -EIO; 738 | } 739 | if ((flags & SSH_FILEXFER_ATTR_EXTENDED)) { 740 | uint32_t extcount; 741 | unsigned i; 742 | if (buf_get_uint32(buf, &extcount) == -1) 743 | return -EIO; 744 | for (i = 0; i < extcount; i++) { 745 | struct buffer tmp; 746 | if (buf_get_data(buf, &tmp) == -1) 747 | return -EIO; 748 | buf_free(&tmp); 749 | if (buf_get_data(buf, &tmp) == -1) 750 | return -EIO; 751 | buf_free(&tmp); 752 | } 753 | } 754 | 755 | #ifdef __APPLE__ 756 | if (sshfs.remote_uid_detected) { 757 | if (uid == sshfs.remote_uid) 758 | uid = sshfs.local_uid; 759 | if (gid == sshfs.remote_gid) 760 | gid = sshfs.local_gid; 761 | } 762 | #else 763 | if (sshfs.remote_uid_detected && uid == sshfs.remote_uid) 764 | uid = sshfs.local_uid; 765 | #endif 766 | if (sshfs.idmap == IDMAP_FILE && sshfs.uid_map) 767 | if (translate_id(&uid, sshfs.uid_map) == -1) 768 | return -EPERM; 769 | if (sshfs.idmap == IDMAP_FILE && sshfs.gid_map) 770 | if (translate_id(&gid, sshfs.gid_map) == -1) 771 | return -EPERM; 772 | 773 | memset(stbuf, 0, sizeof(struct stat)); 774 | stbuf->st_mode = mode; 775 | stbuf->st_nlink = 1; 776 | stbuf->st_size = size; 777 | if (sshfs.blksize) { 778 | stbuf->st_blksize = sshfs.blksize; 779 | stbuf->st_blocks = ((size + sshfs.blksize - 1) & 780 | ~((unsigned long long) sshfs.blksize - 1)) >> 9; 781 | } 782 | stbuf->st_uid = uid; 783 | stbuf->st_gid = gid; 784 | stbuf->st_atime = atime; 785 | stbuf->st_ctime = stbuf->st_mtime = mtime; 786 | return 0; 787 | } 788 | 789 | static int buf_get_statvfs(struct buffer *buf, struct statvfs *stbuf) 790 | { 791 | uint64_t bsize; 792 | uint64_t frsize; 793 | uint64_t blocks; 794 | uint64_t bfree; 795 | uint64_t bavail; 796 | uint64_t files; 797 | uint64_t ffree; 798 | uint64_t favail; 799 | uint64_t fsid; 800 | uint64_t flag; 801 | uint64_t namemax; 802 | 803 | if (buf_get_uint64(buf, &bsize) == -1 || 804 | buf_get_uint64(buf, &frsize) == -1 || 805 | buf_get_uint64(buf, &blocks) == -1 || 806 | buf_get_uint64(buf, &bfree) == -1 || 807 | buf_get_uint64(buf, &bavail) == -1 || 808 | buf_get_uint64(buf, &files) == -1 || 809 | buf_get_uint64(buf, &ffree) == -1 || 810 | buf_get_uint64(buf, &favail) == -1 || 811 | buf_get_uint64(buf, &fsid) == -1 || 812 | buf_get_uint64(buf, &flag) == -1 || 813 | buf_get_uint64(buf, &namemax) == -1) { 814 | return -1; 815 | } 816 | 817 | memset(stbuf, 0, sizeof(struct statvfs)); 818 | stbuf->f_bsize = bsize; 819 | stbuf->f_frsize = frsize; 820 | stbuf->f_blocks = blocks; 821 | stbuf->f_bfree = bfree; 822 | stbuf->f_bavail = bavail; 823 | stbuf->f_files = files; 824 | stbuf->f_ffree = ffree; 825 | stbuf->f_favail = favail; 826 | stbuf->f_namemax = namemax; 827 | 828 | return 0; 829 | } 830 | 831 | static int buf_get_entries(struct buffer *buf, fuse_cache_dirh_t h, 832 | fuse_cache_dirfil_t filler) 833 | { 834 | uint32_t count; 835 | unsigned i; 836 | 837 | if (buf_get_uint32(buf, &count) == -1) 838 | return -EIO; 839 | 840 | for (i = 0; i < count; i++) { 841 | int err = -1; 842 | char *name; 843 | char *longname; 844 | struct stat stbuf; 845 | if (buf_get_string(buf, &name) == -1) 846 | return -EIO; 847 | if (buf_get_string(buf, &longname) != -1) { 848 | free(longname); 849 | err = buf_get_attrs(buf, &stbuf, NULL); 850 | if (!err) { 851 | if (sshfs.follow_symlinks && 852 | S_ISLNK(stbuf.st_mode)) { 853 | stbuf.st_mode = 0; 854 | } 855 | filler(h, name, &stbuf); 856 | } 857 | } 858 | free(name); 859 | if (err) 860 | return err; 861 | } 862 | return 0; 863 | } 864 | 865 | static void ssh_add_arg(const char *arg) 866 | { 867 | if (fuse_opt_add_arg(&sshfs.ssh_args, arg) == -1) 868 | _exit(1); 869 | } 870 | 871 | #ifdef SSH_NODELAY_WORKAROUND 872 | static int do_ssh_nodelay_workaround(void) 873 | { 874 | #ifdef __APPLE__ 875 | char *oldpreload = getenv("DYLD_INSERT_LIBRARIES"); 876 | #else 877 | char *oldpreload = getenv("LD_PRELOAD"); 878 | #endif 879 | char *newpreload; 880 | char sopath[PATH_MAX]; 881 | int res; 882 | 883 | #ifdef __APPLE__ 884 | char *sshfs_program_path_base = NULL; 885 | if (!sshfs_program_path[0]) { 886 | goto nobundle; 887 | } 888 | sshfs_program_path_base = dirname(sshfs_program_path); 889 | if (!sshfs_program_path_base) { 890 | goto nobundle; 891 | } 892 | snprintf(sopath, sizeof(sopath), "%s/%s", sshfs_program_path_base, 893 | SSHNODELAY_SO); 894 | res = access(sopath, R_OK); 895 | if (res == -1) { 896 | goto nobundle; 897 | } 898 | goto pathok; 899 | nobundle: 900 | #endif 901 | snprintf(sopath, sizeof(sopath), "%s/%s", LIBDIR, SSHNODELAY_SO); 902 | res = access(sopath, R_OK); 903 | if (res == -1) { 904 | char *s; 905 | if (!realpath(sshfs.progname, sopath)) 906 | return -1; 907 | 908 | s = strrchr(sopath, '/'); 909 | if (!s) 910 | s = sopath; 911 | else 912 | s++; 913 | 914 | if (s + strlen(SSHNODELAY_SO) >= sopath + sizeof(sopath)) 915 | return -1; 916 | 917 | strcpy(s, SSHNODELAY_SO); 918 | res = access(sopath, R_OK); 919 | if (res == -1) { 920 | fprintf(stderr, "sshfs: cannot find %s\n", 921 | SSHNODELAY_SO); 922 | return -1; 923 | } 924 | } 925 | #ifdef __APPLE__ 926 | pathok: 927 | #endif 928 | 929 | newpreload = g_strdup_printf("%s%s%s", 930 | oldpreload ? oldpreload : "", 931 | oldpreload ? " " : "", 932 | sopath); 933 | 934 | #ifdef __APPLE__ 935 | if (!newpreload || setenv("DYLD_INSERT_LIBRARIES", newpreload, 1) == -1) 936 | fprintf(stderr, "warning: failed set DYLD_INSERT_LIBRARIES for ssh nodelay workaround\n"); 937 | #else 938 | if (!newpreload || setenv("LD_PRELOAD", newpreload, 1) == -1) { 939 | fprintf(stderr, "warning: failed set LD_PRELOAD " 940 | "for ssh nodelay workaround\n"); 941 | } 942 | #endif 943 | g_free(newpreload); 944 | return 0; 945 | } 946 | #endif 947 | 948 | static int pty_expect_loop(void) 949 | { 950 | int res; 951 | char buf[256]; 952 | const char *passwd_str = "assword:"; 953 | int timeout = 60 * 1000; /* 1min timeout for the prompt to appear */ 954 | int passwd_len = strlen(passwd_str); 955 | int len = 0; 956 | char c; 957 | 958 | while (1) { 959 | struct pollfd fds[2]; 960 | 961 | fds[0].fd = sshfs.rfd; 962 | fds[0].events = POLLIN; 963 | fds[1].fd = sshfs.ptyfd; 964 | fds[1].events = POLLIN; 965 | res = poll(fds, 2, timeout); 966 | if (res == -1) { 967 | perror("poll"); 968 | return -1; 969 | } 970 | if (res == 0) { 971 | fprintf(stderr, "Timeout waiting for prompt\n"); 972 | return -1; 973 | } 974 | if (fds[0].revents) { 975 | /* 976 | * Something happened on stdout of ssh, this 977 | * either means, that we are connected, or 978 | * that we are disconnected. In any case the 979 | * password doesn't matter any more. 980 | */ 981 | break; 982 | } 983 | 984 | res = read(sshfs.ptyfd, &c, 1); 985 | if (res == -1) { 986 | perror("read"); 987 | return -1; 988 | } 989 | if (res == 0) { 990 | fprintf(stderr, "EOF while waiting for prompt\n"); 991 | return -1; 992 | } 993 | buf[len] = c; 994 | len++; 995 | if (len == passwd_len) { 996 | if (memcmp(buf, passwd_str, passwd_len) == 0) { 997 | write(sshfs.ptyfd, sshfs.password, 998 | strlen(sshfs.password)); 999 | } 1000 | memmove(buf, buf + 1, passwd_len - 1); 1001 | len--; 1002 | } 1003 | } 1004 | 1005 | if (!sshfs.reconnect) { 1006 | size_t size = getpagesize(); 1007 | 1008 | memset(sshfs.password, 0, size); 1009 | munmap(sshfs.password, size); 1010 | sshfs.password = NULL; 1011 | } 1012 | 1013 | return 0; 1014 | } 1015 | 1016 | static int pty_master(char **name) 1017 | { 1018 | int mfd; 1019 | 1020 | mfd = open("/dev/ptmx", O_RDWR | O_NOCTTY); 1021 | if (mfd == -1) { 1022 | perror("failed to open pty"); 1023 | return -1; 1024 | } 1025 | if (grantpt(mfd) != 0) { 1026 | perror("grantpt"); 1027 | return -1; 1028 | } 1029 | if (unlockpt(mfd) != 0) { 1030 | perror("unlockpt"); 1031 | return -1; 1032 | } 1033 | *name = ptsname(mfd); 1034 | 1035 | return mfd; 1036 | } 1037 | 1038 | static void replace_arg(char **argp, const char *newarg) 1039 | { 1040 | free(*argp); 1041 | *argp = strdup(newarg); 1042 | if (*argp == NULL) { 1043 | fprintf(stderr, "sshfs: memory allocation failed\n"); 1044 | abort(); 1045 | } 1046 | } 1047 | 1048 | static int start_ssh(void) 1049 | { 1050 | char *ptyname = NULL; 1051 | int sockpair[2]; 1052 | int pid; 1053 | 1054 | if (sshfs.password_stdin) { 1055 | 1056 | sshfs.ptyfd = pty_master(&ptyname); 1057 | if (sshfs.ptyfd == -1) 1058 | return -1; 1059 | 1060 | sshfs.ptyslavefd = open(ptyname, O_RDWR | O_NOCTTY); 1061 | if (sshfs.ptyslavefd == -1) 1062 | return -1; 1063 | } 1064 | 1065 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) == -1) { 1066 | perror("failed to create socket pair"); 1067 | return -1; 1068 | } 1069 | sshfs.rfd = sockpair[0]; 1070 | sshfs.wfd = sockpair[0]; 1071 | 1072 | pid = fork(); 1073 | if (pid == -1) { 1074 | perror("failed to fork"); 1075 | close(sockpair[1]); 1076 | return -1; 1077 | } else if (pid == 0) { 1078 | int devnull; 1079 | 1080 | #ifdef SSH_NODELAY_WORKAROUND 1081 | if (sshfs.nodelay_workaround && 1082 | do_ssh_nodelay_workaround() == -1) { 1083 | fprintf(stderr, 1084 | "warning: ssh nodelay workaround disabled\n"); 1085 | } 1086 | #endif 1087 | 1088 | if (sshfs.nodelaysrv_workaround) { 1089 | int i; 1090 | /* 1091 | * Hack to work around missing TCP_NODELAY 1092 | * setting in sshd 1093 | */ 1094 | for (i = 1; i < sshfs.ssh_args.argc; i++) { 1095 | if (strcmp(sshfs.ssh_args.argv[i], "-x") == 0) { 1096 | replace_arg(&sshfs.ssh_args.argv[i], 1097 | "-X"); 1098 | break; 1099 | } 1100 | } 1101 | } 1102 | 1103 | devnull = open("/dev/null", O_WRONLY); 1104 | 1105 | if (dup2(sockpair[1], 0) == -1 || dup2(sockpair[1], 1) == -1) { 1106 | perror("failed to redirect input/output"); 1107 | _exit(1); 1108 | } 1109 | if (!sshfs.foreground && devnull != -1) 1110 | dup2(devnull, 2); 1111 | 1112 | close(devnull); 1113 | close(sockpair[0]); 1114 | close(sockpair[1]); 1115 | 1116 | switch (fork()) { 1117 | case -1: 1118 | perror("failed to fork"); 1119 | _exit(1); 1120 | case 0: 1121 | break; 1122 | default: 1123 | _exit(0); 1124 | } 1125 | chdir("/"); 1126 | 1127 | if (sshfs.password_stdin) { 1128 | int sfd; 1129 | 1130 | setsid(); 1131 | sfd = open(ptyname, O_RDWR); 1132 | if (sfd == -1) { 1133 | perror(ptyname); 1134 | _exit(1); 1135 | } 1136 | close(sfd); 1137 | close(sshfs.ptyslavefd); 1138 | close(sshfs.ptyfd); 1139 | } 1140 | 1141 | if (sshfs.debug) { 1142 | int i; 1143 | 1144 | fprintf(stderr, "executing"); 1145 | for (i = 0; i < sshfs.ssh_args.argc; i++) 1146 | fprintf(stderr, " <%s>", 1147 | sshfs.ssh_args.argv[i]); 1148 | fprintf(stderr, "\n"); 1149 | } 1150 | 1151 | execvp(sshfs.ssh_args.argv[0], sshfs.ssh_args.argv); 1152 | fprintf(stderr, "failed to execute '%s': %s\n", 1153 | sshfs.ssh_args.argv[0], strerror(errno)); 1154 | _exit(1); 1155 | } 1156 | waitpid(pid, NULL, 0); 1157 | close(sockpair[1]); 1158 | return 0; 1159 | } 1160 | 1161 | static int connect_slave() 1162 | { 1163 | sshfs.rfd = STDIN_FILENO; 1164 | sshfs.wfd = STDOUT_FILENO; 1165 | return 0; 1166 | } 1167 | 1168 | static int connect_to(char *host, char *port) 1169 | { 1170 | int err; 1171 | int sock; 1172 | int opt; 1173 | struct addrinfo *ai; 1174 | struct addrinfo hint; 1175 | 1176 | memset(&hint, 0, sizeof(hint)); 1177 | hint.ai_family = PF_INET; 1178 | hint.ai_socktype = SOCK_STREAM; 1179 | err = getaddrinfo(host, port, &hint, &ai); 1180 | if (err) { 1181 | fprintf(stderr, "failed to resolve %s:%s: %s\n", host, port, 1182 | gai_strerror(err)); 1183 | return -1; 1184 | } 1185 | sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 1186 | if (sock == -1) { 1187 | perror("failed to create socket"); 1188 | return -1; 1189 | } 1190 | err = connect(sock, ai->ai_addr, ai->ai_addrlen); 1191 | if (err == -1) { 1192 | perror("failed to connect"); 1193 | return -1; 1194 | } 1195 | opt = 1; 1196 | err = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); 1197 | if (err == -1) 1198 | perror("warning: failed to set TCP_NODELAY"); 1199 | 1200 | freeaddrinfo(ai); 1201 | 1202 | sshfs.rfd = sock; 1203 | sshfs.wfd = sock; 1204 | return 0; 1205 | } 1206 | 1207 | static int do_write(struct iovec *iov, size_t count) 1208 | { 1209 | int res; 1210 | while (count) { 1211 | res = writev(sshfs.wfd, iov, count); 1212 | if (res == -1) { 1213 | perror("write"); 1214 | return -1; 1215 | } else if (res == 0) { 1216 | fprintf(stderr, "zero write\n"); 1217 | return -1; 1218 | } 1219 | do { 1220 | if ((unsigned) res < iov->iov_len) { 1221 | iov->iov_len -= res; 1222 | iov->iov_base += res; 1223 | break; 1224 | } else { 1225 | res -= iov->iov_len; 1226 | count --; 1227 | iov ++; 1228 | } 1229 | } while(count); 1230 | } 1231 | return 0; 1232 | } 1233 | 1234 | static uint32_t sftp_get_id(void) 1235 | { 1236 | static uint32_t idctr; 1237 | return idctr++; 1238 | } 1239 | 1240 | static void buf_to_iov(const struct buffer *buf, struct iovec *iov) 1241 | { 1242 | iov->iov_base = buf->p; 1243 | iov->iov_len = buf->len; 1244 | } 1245 | 1246 | static size_t iov_length(const struct iovec *iov, unsigned long nr_segs) 1247 | { 1248 | unsigned long seg; 1249 | size_t ret = 0; 1250 | 1251 | for (seg = 0; seg < nr_segs; seg++) 1252 | ret += iov[seg].iov_len; 1253 | return ret; 1254 | } 1255 | 1256 | #define SFTP_MAX_IOV 3 1257 | 1258 | static int sftp_send_iov(uint8_t type, uint32_t id, struct iovec iov[], 1259 | size_t count) 1260 | { 1261 | int res; 1262 | struct buffer buf; 1263 | struct iovec iovout[SFTP_MAX_IOV]; 1264 | unsigned i; 1265 | unsigned nout = 0; 1266 | 1267 | assert(count <= SFTP_MAX_IOV - 1); 1268 | buf_init(&buf, 9); 1269 | buf_add_uint32(&buf, iov_length(iov, count) + 5); 1270 | buf_add_uint8(&buf, type); 1271 | buf_add_uint32(&buf, id); 1272 | buf_to_iov(&buf, &iovout[nout++]); 1273 | for (i = 0; i < count; i++) 1274 | iovout[nout++] = iov[i]; 1275 | pthread_mutex_lock(&sshfs.lock_write); 1276 | res = do_write(iovout, nout); 1277 | pthread_mutex_unlock(&sshfs.lock_write); 1278 | buf_free(&buf); 1279 | return res; 1280 | } 1281 | 1282 | static int do_read(struct buffer *buf) 1283 | { 1284 | int res; 1285 | uint8_t *p = buf->p; 1286 | size_t size = buf->size; 1287 | while (size) { 1288 | res = read(sshfs.rfd, p, size); 1289 | if (res == -1) { 1290 | perror("read"); 1291 | return -1; 1292 | } else if (res == 0) { 1293 | fprintf(stderr, "remote host has disconnected\n"); 1294 | return -1; 1295 | } 1296 | size -= res; 1297 | p += res; 1298 | } 1299 | return 0; 1300 | } 1301 | 1302 | static int sftp_read(uint8_t *type, struct buffer *buf) 1303 | { 1304 | int res; 1305 | struct buffer buf2; 1306 | uint32_t len; 1307 | buf_init(&buf2, 5); 1308 | res = do_read(&buf2); 1309 | if (res != -1) { 1310 | if (buf_get_uint32(&buf2, &len) == -1) 1311 | return -1; 1312 | if (len > MAX_REPLY_LEN) { 1313 | fprintf(stderr, "reply len too large: %u\n", len); 1314 | return -1; 1315 | } 1316 | if (buf_get_uint8(&buf2, type) == -1) 1317 | return -1; 1318 | buf_init(buf, len - 1); 1319 | res = do_read(buf); 1320 | } 1321 | buf_free(&buf2); 1322 | return res; 1323 | } 1324 | 1325 | static void request_free(struct request *req) 1326 | { 1327 | buf_free(&req->reply); 1328 | sem_destroy(&req->ready); 1329 | g_free(req); 1330 | } 1331 | 1332 | static void chunk_free(struct read_chunk *chunk) 1333 | { 1334 | while (!list_empty(&chunk->reqs)) { 1335 | struct read_req *rreq; 1336 | 1337 | rreq = list_entry(chunk->reqs.prev, struct read_req, list); 1338 | list_del(&rreq->list); 1339 | buf_free(&rreq->data); 1340 | g_free(rreq); 1341 | } 1342 | g_free(chunk); 1343 | } 1344 | 1345 | static void chunk_put(struct read_chunk *chunk) 1346 | { 1347 | if (chunk) { 1348 | chunk->refs--; 1349 | if (!chunk->refs) 1350 | chunk_free(chunk); 1351 | } 1352 | } 1353 | 1354 | static void chunk_put_locked(struct read_chunk *chunk) 1355 | { 1356 | pthread_mutex_lock(&sshfs.lock); 1357 | chunk_put(chunk); 1358 | pthread_mutex_unlock(&sshfs.lock); 1359 | } 1360 | 1361 | static int clean_req(void *key_, struct request *req) 1362 | { 1363 | (void) key_; 1364 | 1365 | req->error = -EIO; 1366 | if (req->want_reply) 1367 | sem_post(&req->ready); 1368 | else { 1369 | if (req->end_func) 1370 | req->end_func(req); 1371 | request_free(req); 1372 | } 1373 | return TRUE; 1374 | } 1375 | 1376 | static int process_one_request(void) 1377 | { 1378 | int res; 1379 | struct buffer buf; 1380 | uint8_t type; 1381 | struct request *req; 1382 | uint32_t id; 1383 | 1384 | buf_init(&buf, 0); 1385 | res = sftp_read(&type, &buf); 1386 | if (res == -1) 1387 | return -1; 1388 | if (buf_get_uint32(&buf, &id) == -1) 1389 | return -1; 1390 | 1391 | pthread_mutex_lock(&sshfs.lock); 1392 | req = (struct request *) 1393 | g_hash_table_lookup(sshfs.reqtab, GUINT_TO_POINTER(id)); 1394 | if (req == NULL) 1395 | fprintf(stderr, "request %i not found\n", id); 1396 | else { 1397 | int was_over; 1398 | 1399 | was_over = sshfs.outstanding_len > sshfs.max_outstanding_len; 1400 | sshfs.outstanding_len -= req->len; 1401 | if (was_over && 1402 | sshfs.outstanding_len <= sshfs.max_outstanding_len) { 1403 | pthread_cond_broadcast(&sshfs.outstanding_cond); 1404 | } 1405 | g_hash_table_remove(sshfs.reqtab, GUINT_TO_POINTER(id)); 1406 | } 1407 | pthread_mutex_unlock(&sshfs.lock); 1408 | if (req != NULL) { 1409 | if (sshfs.debug) { 1410 | struct timeval now; 1411 | unsigned int difftime; 1412 | unsigned msgsize = buf.size + 5; 1413 | 1414 | gettimeofday(&now, NULL); 1415 | difftime = (now.tv_sec - req->start.tv_sec) * 1000; 1416 | difftime += (now.tv_usec - req->start.tv_usec) / 1000; 1417 | DEBUG(" [%05i] %14s %8ubytes (%ims)\n", id, 1418 | type_name(type), msgsize, difftime); 1419 | 1420 | if (difftime < sshfs.min_rtt || !sshfs.num_received) 1421 | sshfs.min_rtt = difftime; 1422 | if (difftime > sshfs.max_rtt) 1423 | sshfs.max_rtt = difftime; 1424 | sshfs.total_rtt += difftime; 1425 | sshfs.num_received++; 1426 | sshfs.bytes_received += msgsize; 1427 | } 1428 | req->reply = buf; 1429 | req->reply_type = type; 1430 | req->replied = 1; 1431 | if (req->want_reply) 1432 | sem_post(&req->ready); 1433 | else { 1434 | if (req->end_func) { 1435 | pthread_mutex_lock(&sshfs.lock); 1436 | req->end_func(req); 1437 | pthread_mutex_unlock(&sshfs.lock); 1438 | } 1439 | request_free(req); 1440 | } 1441 | } else 1442 | buf_free(&buf); 1443 | 1444 | return 0; 1445 | } 1446 | 1447 | static void close_conn(void) 1448 | { 1449 | close(sshfs.rfd); 1450 | if (sshfs.rfd != sshfs.wfd) 1451 | close(sshfs.wfd); 1452 | sshfs.rfd = -1; 1453 | sshfs.wfd = -1; 1454 | if (sshfs.ptyfd != -1) { 1455 | close(sshfs.ptyfd); 1456 | sshfs.ptyfd = -1; 1457 | } 1458 | if (sshfs.ptyslavefd != -1) { 1459 | close(sshfs.ptyslavefd); 1460 | sshfs.ptyslavefd = -1; 1461 | } 1462 | } 1463 | 1464 | static void *process_requests(void *data_) 1465 | { 1466 | (void) data_; 1467 | 1468 | while (1) { 1469 | if (process_one_request() == -1) 1470 | break; 1471 | } 1472 | 1473 | pthread_mutex_lock(&sshfs.lock); 1474 | sshfs.processing_thread_started = 0; 1475 | close_conn(); 1476 | g_hash_table_foreach_remove(sshfs.reqtab, (GHRFunc) clean_req, NULL); 1477 | sshfs.connver ++; 1478 | sshfs.outstanding_len = 0; 1479 | pthread_cond_broadcast(&sshfs.outstanding_cond); 1480 | pthread_mutex_unlock(&sshfs.lock); 1481 | 1482 | if (!sshfs.reconnect) { 1483 | /* harakiri */ 1484 | kill(getpid(), SIGTERM); 1485 | } 1486 | return NULL; 1487 | } 1488 | 1489 | static int sftp_init_reply_ok(struct buffer *buf, uint32_t *version) 1490 | { 1491 | uint32_t len; 1492 | uint8_t type; 1493 | 1494 | if (buf_get_uint32(buf, &len) == -1) 1495 | return -1; 1496 | 1497 | if (len < 5 || len > MAX_REPLY_LEN) 1498 | return 1; 1499 | 1500 | if (buf_get_uint8(buf, &type) == -1) 1501 | return -1; 1502 | 1503 | if (type != SSH_FXP_VERSION) 1504 | return 1; 1505 | 1506 | if (buf_get_uint32(buf, version) == -1) 1507 | return -1; 1508 | 1509 | DEBUG("Server version: %u\n", *version); 1510 | 1511 | if (len > 5) { 1512 | struct buffer buf2; 1513 | 1514 | buf_init(&buf2, len - 5); 1515 | if (do_read(&buf2) == -1) { 1516 | buf_free(&buf2); 1517 | return -1; 1518 | } 1519 | 1520 | do { 1521 | char *ext; 1522 | char *extdata; 1523 | 1524 | if (buf_get_string(&buf2, &ext) == -1 || 1525 | buf_get_string(&buf2, &extdata) == -1) { 1526 | buf_free(&buf2); 1527 | return -1; 1528 | } 1529 | 1530 | DEBUG("Extension: %s <%s>\n", ext, extdata); 1531 | 1532 | if (strcmp(ext, SFTP_EXT_POSIX_RENAME) == 0 && 1533 | strcmp(extdata, "1") == 0) { 1534 | sshfs.ext_posix_rename = 1; 1535 | sshfs.rename_workaround = 0; 1536 | } 1537 | if (strcmp(ext, SFTP_EXT_STATVFS) == 0 && 1538 | strcmp(extdata, "2") == 0) 1539 | sshfs.ext_statvfs = 1; 1540 | if (strcmp(ext, SFTP_EXT_HARDLINK) == 0 && 1541 | strcmp(extdata, "1") == 0) 1542 | sshfs.ext_hardlink = 1; 1543 | } while (buf2.len < buf2.size); 1544 | buf_free(&buf2); 1545 | } 1546 | return 0; 1547 | } 1548 | 1549 | static int sftp_find_init_reply(uint32_t *version) 1550 | { 1551 | int res; 1552 | struct buffer buf; 1553 | 1554 | buf_init(&buf, 9); 1555 | res = do_read(&buf); 1556 | while (res != -1) { 1557 | struct buffer buf2; 1558 | 1559 | res = sftp_init_reply_ok(&buf, version); 1560 | if (res <= 0) 1561 | break; 1562 | 1563 | /* Iterate over any rubbish until the version reply is found */ 1564 | DEBUG("%c", *buf.p); 1565 | memmove(buf.p, buf.p + 1, buf.size - 1); 1566 | buf.len = 0; 1567 | buf2.p = buf.p + buf.size - 1; 1568 | buf2.size = 1; 1569 | res = do_read(&buf2); 1570 | } 1571 | buf_free(&buf); 1572 | return res; 1573 | } 1574 | 1575 | static int sftp_init() 1576 | { 1577 | int res = -1; 1578 | uint32_t version = 0; 1579 | struct buffer buf; 1580 | buf_init(&buf, 0); 1581 | if (sftp_send_iov(SSH_FXP_INIT, PROTO_VERSION, NULL, 0) == -1) 1582 | goto out; 1583 | 1584 | if (sshfs.password_stdin && pty_expect_loop() == -1) 1585 | goto out; 1586 | 1587 | if (sftp_find_init_reply(&version) == -1) 1588 | goto out; 1589 | 1590 | sshfs.server_version = version; 1591 | if (version > PROTO_VERSION) { 1592 | fprintf(stderr, 1593 | "Warning: server uses version: %i, we support: %i\n", 1594 | version, PROTO_VERSION); 1595 | } 1596 | res = 0; 1597 | 1598 | out: 1599 | buf_free(&buf); 1600 | return res; 1601 | } 1602 | 1603 | static int sftp_error_to_errno(uint32_t error) 1604 | { 1605 | switch (error) { 1606 | case SSH_FX_OK: return 0; 1607 | case SSH_FX_NO_SUCH_FILE: return ENOENT; 1608 | case SSH_FX_PERMISSION_DENIED: return EACCES; 1609 | case SSH_FX_FAILURE: return EPERM; 1610 | case SSH_FX_BAD_MESSAGE: return EBADMSG; 1611 | case SSH_FX_NO_CONNECTION: return ENOTCONN; 1612 | case SSH_FX_CONNECTION_LOST: return ECONNABORTED; 1613 | case SSH_FX_OP_UNSUPPORTED: return EOPNOTSUPP; 1614 | default: return EIO; 1615 | } 1616 | } 1617 | 1618 | static void sftp_detect_uid() 1619 | { 1620 | int flags; 1621 | uint32_t id = sftp_get_id(); 1622 | uint32_t replid; 1623 | uint8_t type; 1624 | struct buffer buf; 1625 | struct stat stbuf; 1626 | struct iovec iov[1]; 1627 | 1628 | buf_init(&buf, 5); 1629 | buf_add_string(&buf, "."); 1630 | buf_to_iov(&buf, &iov[0]); 1631 | if (sftp_send_iov(SSH_FXP_STAT, id, iov, 1) == -1) 1632 | goto out; 1633 | buf_clear(&buf); 1634 | if (sftp_read(&type, &buf) == -1) 1635 | goto out; 1636 | if (type != SSH_FXP_ATTRS && type != SSH_FXP_STATUS) { 1637 | fprintf(stderr, "protocol error\n"); 1638 | goto out; 1639 | } 1640 | if (buf_get_uint32(&buf, &replid) == -1) 1641 | goto out; 1642 | if (replid != id) { 1643 | fprintf(stderr, "bad reply ID\n"); 1644 | goto out; 1645 | } 1646 | if (type == SSH_FXP_STATUS) { 1647 | uint32_t serr; 1648 | if (buf_get_uint32(&buf, &serr) == -1) 1649 | goto out; 1650 | 1651 | fprintf(stderr, "failed to stat home directory (%i)\n", serr); 1652 | goto out; 1653 | } 1654 | if (buf_get_attrs(&buf, &stbuf, &flags) != 0) 1655 | goto out; 1656 | 1657 | if (!(flags & SSH_FILEXFER_ATTR_UIDGID)) 1658 | goto out; 1659 | 1660 | sshfs.remote_uid = stbuf.st_uid; 1661 | sshfs.local_uid = getuid(); 1662 | #ifdef __APPLE__ 1663 | sshfs.remote_gid = stbuf.st_gid; 1664 | sshfs.local_gid = getgid(); 1665 | #endif 1666 | sshfs.remote_uid_detected = 1; 1667 | DEBUG("remote_uid = %i\n", sshfs.remote_uid); 1668 | 1669 | out: 1670 | if (!sshfs.remote_uid_detected) 1671 | fprintf(stderr, "failed to detect remote user ID\n"); 1672 | 1673 | buf_free(&buf); 1674 | } 1675 | 1676 | static int sftp_check_root(const char *base_path) 1677 | { 1678 | int flags; 1679 | uint32_t id = sftp_get_id(); 1680 | uint32_t replid; 1681 | uint8_t type; 1682 | struct buffer buf; 1683 | struct stat stbuf; 1684 | struct iovec iov[1]; 1685 | int err = -1; 1686 | const char *remote_dir = base_path[0] ? base_path : "."; 1687 | 1688 | buf_init(&buf, 0); 1689 | buf_add_string(&buf, remote_dir); 1690 | buf_to_iov(&buf, &iov[0]); 1691 | if (sftp_send_iov(SSH_FXP_STAT, id, iov, 1) == -1) 1692 | goto out; 1693 | buf_clear(&buf); 1694 | if (sftp_read(&type, &buf) == -1) 1695 | goto out; 1696 | if (type != SSH_FXP_ATTRS && type != SSH_FXP_STATUS) { 1697 | fprintf(stderr, "protocol error\n"); 1698 | goto out; 1699 | } 1700 | if (buf_get_uint32(&buf, &replid) == -1) 1701 | goto out; 1702 | if (replid != id) { 1703 | fprintf(stderr, "bad reply ID\n"); 1704 | goto out; 1705 | } 1706 | if (type == SSH_FXP_STATUS) { 1707 | uint32_t serr; 1708 | if (buf_get_uint32(&buf, &serr) == -1) 1709 | goto out; 1710 | 1711 | fprintf(stderr, "%s:%s: %s\n", sshfs.host, remote_dir, 1712 | strerror(sftp_error_to_errno(serr))); 1713 | 1714 | goto out; 1715 | } 1716 | 1717 | int err2 = buf_get_attrs(&buf, &stbuf, &flags); 1718 | if (err2) { 1719 | err = err2; 1720 | goto out; 1721 | } 1722 | 1723 | if (!(flags & SSH_FILEXFER_ATTR_PERMISSIONS)) 1724 | goto out; 1725 | 1726 | if (S_ISDIR(sshfs.mnt_mode) && !S_ISDIR(stbuf.st_mode)) { 1727 | fprintf(stderr, "%s:%s: Not a directory\n", sshfs.host, 1728 | remote_dir); 1729 | goto out; 1730 | } 1731 | if ((sshfs.mnt_mode ^ stbuf.st_mode) & S_IFMT) { 1732 | fprintf(stderr, "%s:%s: type of file differs from mountpoint\n", 1733 | sshfs.host, remote_dir); 1734 | goto out; 1735 | } 1736 | 1737 | err = 0; 1738 | 1739 | out: 1740 | buf_free(&buf); 1741 | return err; 1742 | } 1743 | 1744 | static int connect_remote(void) 1745 | { 1746 | int err; 1747 | 1748 | if (sshfs.slave) 1749 | err = connect_slave(); 1750 | else if (sshfs.directport) 1751 | err = connect_to(sshfs.host, sshfs.directport); 1752 | else 1753 | err = start_ssh(); 1754 | if (!err) 1755 | err = sftp_init(); 1756 | 1757 | if (err) 1758 | close_conn(); 1759 | else 1760 | sshfs.num_connect++; 1761 | 1762 | return err; 1763 | } 1764 | 1765 | static int start_processing_thread(void) 1766 | { 1767 | int err; 1768 | pthread_t thread_id; 1769 | sigset_t oldset; 1770 | sigset_t newset; 1771 | 1772 | if (sshfs.processing_thread_started) 1773 | return 0; 1774 | 1775 | if (sshfs.rfd == -1) { 1776 | err = connect_remote(); 1777 | if (err) 1778 | return -EIO; 1779 | } 1780 | 1781 | if (sshfs.detect_uid) { 1782 | sftp_detect_uid(); 1783 | sshfs.detect_uid = 0; 1784 | } 1785 | 1786 | sigemptyset(&newset); 1787 | sigaddset(&newset, SIGTERM); 1788 | sigaddset(&newset, SIGINT); 1789 | sigaddset(&newset, SIGHUP); 1790 | sigaddset(&newset, SIGQUIT); 1791 | pthread_sigmask(SIG_BLOCK, &newset, &oldset); 1792 | err = pthread_create(&thread_id, NULL, process_requests, NULL); 1793 | if (err) { 1794 | fprintf(stderr, "failed to create thread: %s\n", strerror(err)); 1795 | return -EIO; 1796 | } 1797 | pthread_detach(thread_id); 1798 | pthread_sigmask(SIG_SETMASK, &oldset, NULL); 1799 | sshfs.processing_thread_started = 1; 1800 | return 0; 1801 | } 1802 | 1803 | #if FUSE_VERSION >= 26 1804 | static void *sshfs_init(struct fuse_conn_info *conn) 1805 | #else 1806 | static void *sshfs_init(void) 1807 | #endif 1808 | { 1809 | #if FUSE_VERSION >= 26 1810 | /* Readahead should be done by kernel or sshfs but not both */ 1811 | if (conn->async_read) 1812 | sshfs.sync_read = 1; 1813 | #endif 1814 | 1815 | if (!sshfs.delay_connect) 1816 | start_processing_thread(); 1817 | 1818 | return NULL; 1819 | } 1820 | 1821 | static int sftp_request_wait(struct request *req, uint8_t type, 1822 | uint8_t expect_type, struct buffer *outbuf) 1823 | { 1824 | int err; 1825 | 1826 | if (req->error) { 1827 | err = req->error; 1828 | goto out; 1829 | } 1830 | while (sem_wait(&req->ready)); 1831 | if (req->error) { 1832 | err = req->error; 1833 | goto out; 1834 | } 1835 | err = -EIO; 1836 | if (req->reply_type != expect_type && 1837 | req->reply_type != SSH_FXP_STATUS) { 1838 | fprintf(stderr, "protocol error\n"); 1839 | goto out; 1840 | } 1841 | if (req->reply_type == SSH_FXP_STATUS) { 1842 | uint32_t serr; 1843 | if (buf_get_uint32(&req->reply, &serr) == -1) 1844 | goto out; 1845 | 1846 | switch (serr) { 1847 | case SSH_FX_OK: 1848 | if (expect_type == SSH_FXP_STATUS) 1849 | err = 0; 1850 | else 1851 | err = -EIO; 1852 | break; 1853 | 1854 | case SSH_FX_EOF: 1855 | if (type == SSH_FXP_READ || type == SSH_FXP_READDIR) 1856 | err = MY_EOF; 1857 | else 1858 | err = -EIO; 1859 | break; 1860 | 1861 | default: 1862 | err = -sftp_error_to_errno(serr); 1863 | } 1864 | } else { 1865 | buf_init(outbuf, req->reply.size - req->reply.len); 1866 | buf_get_mem(&req->reply, outbuf->p, outbuf->size); 1867 | err = 0; 1868 | } 1869 | 1870 | out: 1871 | if (req->end_func) { 1872 | pthread_mutex_lock(&sshfs.lock); 1873 | req->end_func(req); 1874 | pthread_mutex_unlock(&sshfs.lock); 1875 | } 1876 | request_free(req); 1877 | return err; 1878 | } 1879 | 1880 | static int sftp_request_send(uint8_t type, struct iovec *iov, size_t count, 1881 | request_func begin_func, request_func end_func, 1882 | int want_reply, void *data, 1883 | struct request **reqp) 1884 | { 1885 | int err; 1886 | uint32_t id; 1887 | struct request *req = g_new0(struct request, 1); 1888 | 1889 | req->want_reply = want_reply; 1890 | req->end_func = end_func; 1891 | req->data = data; 1892 | sem_init(&req->ready, 0, 0); 1893 | buf_init(&req->reply, 0); 1894 | pthread_mutex_lock(&sshfs.lock); 1895 | if (begin_func) 1896 | begin_func(req); 1897 | id = sftp_get_id(); 1898 | err = start_processing_thread(); 1899 | if (err) { 1900 | pthread_mutex_unlock(&sshfs.lock); 1901 | goto out; 1902 | } 1903 | req->len = iov_length(iov, count) + 9; 1904 | sshfs.outstanding_len += req->len; 1905 | while (sshfs.outstanding_len > sshfs.max_outstanding_len) 1906 | pthread_cond_wait(&sshfs.outstanding_cond, &sshfs.lock); 1907 | 1908 | g_hash_table_insert(sshfs.reqtab, GUINT_TO_POINTER(id), req); 1909 | if (sshfs.debug) { 1910 | gettimeofday(&req->start, NULL); 1911 | sshfs.num_sent++; 1912 | sshfs.bytes_sent += req->len; 1913 | } 1914 | DEBUG("[%05i] %s\n", id, type_name(type)); 1915 | pthread_mutex_unlock(&sshfs.lock); 1916 | 1917 | err = -EIO; 1918 | if (sftp_send_iov(type, id, iov, count) == -1) { 1919 | gboolean rmed; 1920 | 1921 | pthread_mutex_lock(&sshfs.lock); 1922 | rmed = g_hash_table_remove(sshfs.reqtab, GUINT_TO_POINTER(id)); 1923 | pthread_mutex_unlock(&sshfs.lock); 1924 | 1925 | if (!rmed && !want_reply) { 1926 | /* request already freed */ 1927 | return err; 1928 | } 1929 | goto out; 1930 | } 1931 | if (want_reply) 1932 | *reqp = req; 1933 | return 0; 1934 | 1935 | out: 1936 | req->error = err; 1937 | if (!want_reply) 1938 | sftp_request_wait(req, type, 0, NULL); 1939 | else 1940 | *reqp = req; 1941 | 1942 | return err; 1943 | } 1944 | 1945 | 1946 | static int sftp_request_iov(uint8_t type, struct iovec *iov, size_t count, 1947 | uint8_t expect_type, struct buffer *outbuf) 1948 | { 1949 | int err; 1950 | struct request *req; 1951 | 1952 | err = sftp_request_send(type, iov, count, NULL, NULL, expect_type, NULL, 1953 | &req); 1954 | if (expect_type == 0) 1955 | return err; 1956 | 1957 | return sftp_request_wait(req, type, expect_type, outbuf); 1958 | } 1959 | 1960 | static int sftp_request(uint8_t type, const struct buffer *buf, 1961 | uint8_t expect_type, struct buffer *outbuf) 1962 | { 1963 | struct iovec iov; 1964 | 1965 | buf_to_iov(buf, &iov); 1966 | return sftp_request_iov(type, &iov, 1, expect_type, outbuf); 1967 | } 1968 | 1969 | static int sshfs_getattr(const char *path, struct stat *stbuf) 1970 | { 1971 | int err; 1972 | struct buffer buf; 1973 | struct buffer outbuf; 1974 | buf_init(&buf, 0); 1975 | buf_add_path(&buf, path); 1976 | err = sftp_request(sshfs.follow_symlinks ? SSH_FXP_STAT : SSH_FXP_LSTAT, 1977 | &buf, SSH_FXP_ATTRS, &outbuf); 1978 | if (!err) { 1979 | err = buf_get_attrs(&outbuf, stbuf, NULL); 1980 | buf_free(&outbuf); 1981 | } 1982 | buf_free(&buf); 1983 | return err; 1984 | } 1985 | 1986 | static int count_components(const char *p) 1987 | { 1988 | int ctr; 1989 | 1990 | for (; *p == '/'; p++); 1991 | for (ctr = 0; *p; ctr++) { 1992 | for (; *p && *p != '/'; p++); 1993 | for (; *p == '/'; p++); 1994 | } 1995 | return ctr; 1996 | } 1997 | 1998 | static void strip_common(const char **sp, const char **tp) 1999 | { 2000 | const char *s = *sp; 2001 | const char *t = *tp; 2002 | do { 2003 | for (; *s == '/'; s++); 2004 | for (; *t == '/'; t++); 2005 | *tp = t; 2006 | *sp = s; 2007 | for (; *s == *t && *s && *s != '/'; s++, t++); 2008 | } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t)); 2009 | } 2010 | 2011 | static void transform_symlink(const char *path, char **linkp) 2012 | { 2013 | const char *l = *linkp; 2014 | const char *b = sshfs.base_path; 2015 | char *newlink; 2016 | char *s; 2017 | int dotdots; 2018 | int i; 2019 | 2020 | if (l[0] != '/' || b[0] != '/') 2021 | return; 2022 | 2023 | strip_common(&l, &b); 2024 | if (*b) 2025 | return; 2026 | 2027 | strip_common(&l, &path); 2028 | dotdots = count_components(path); 2029 | if (!dotdots) 2030 | return; 2031 | dotdots--; 2032 | 2033 | newlink = malloc(dotdots * 3 + strlen(l) + 2); 2034 | if (!newlink) { 2035 | fprintf(stderr, "sshfs: memory allocation failed\n"); 2036 | abort(); 2037 | } 2038 | for (s = newlink, i = 0; i < dotdots; i++, s += 3) 2039 | strcpy(s, "../"); 2040 | 2041 | if (l[0]) 2042 | strcpy(s, l); 2043 | else if (!dotdots) 2044 | strcpy(s, "."); 2045 | else 2046 | s[0] = '\0'; 2047 | 2048 | free(*linkp); 2049 | *linkp = newlink; 2050 | } 2051 | 2052 | static int sshfs_readlink(const char *path, char *linkbuf, size_t size) 2053 | { 2054 | int err; 2055 | struct buffer buf; 2056 | struct buffer name; 2057 | 2058 | assert(size > 0); 2059 | 2060 | if (sshfs.server_version < 3) 2061 | return -EPERM; 2062 | 2063 | buf_init(&buf, 0); 2064 | buf_add_path(&buf, path); 2065 | err = sftp_request(SSH_FXP_READLINK, &buf, SSH_FXP_NAME, &name); 2066 | if (!err) { 2067 | uint32_t count; 2068 | char *link; 2069 | err = -EIO; 2070 | if(buf_get_uint32(&name, &count) != -1 && count == 1 && 2071 | buf_get_string(&name, &link) != -1) { 2072 | if (sshfs.transform_symlinks) 2073 | transform_symlink(path, &link); 2074 | strncpy(linkbuf, link, size - 1); 2075 | linkbuf[size - 1] = '\0'; 2076 | free(link); 2077 | err = 0; 2078 | } 2079 | buf_free(&name); 2080 | } 2081 | buf_free(&buf); 2082 | return err; 2083 | } 2084 | 2085 | static int sshfs_getdir(const char *path, fuse_cache_dirh_t h, 2086 | fuse_cache_dirfil_t filler) 2087 | { 2088 | int err; 2089 | struct buffer buf; 2090 | struct buffer handle; 2091 | buf_init(&buf, 0); 2092 | buf_add_path(&buf, path); 2093 | err = sftp_request(SSH_FXP_OPENDIR, &buf, SSH_FXP_HANDLE, &handle); 2094 | if (!err) { 2095 | int err2; 2096 | buf_finish(&handle); 2097 | do { 2098 | struct buffer name; 2099 | err = sftp_request(SSH_FXP_READDIR, &handle, SSH_FXP_NAME, &name); 2100 | if (!err) { 2101 | err = buf_get_entries(&name, h, filler); 2102 | buf_free(&name); 2103 | } 2104 | } while (!err); 2105 | if (err == MY_EOF) 2106 | err = 0; 2107 | 2108 | err2 = sftp_request(SSH_FXP_CLOSE, &handle, 0, NULL); 2109 | if (!err) 2110 | err = err2; 2111 | buf_free(&handle); 2112 | } 2113 | buf_free(&buf); 2114 | return err; 2115 | } 2116 | 2117 | static int sshfs_mkdir(const char *path, mode_t mode) 2118 | { 2119 | int err; 2120 | struct buffer buf; 2121 | buf_init(&buf, 0); 2122 | buf_add_path(&buf, path); 2123 | buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); 2124 | buf_add_uint32(&buf, mode); 2125 | err = sftp_request(SSH_FXP_MKDIR, &buf, SSH_FXP_STATUS, NULL); 2126 | buf_free(&buf); 2127 | return err; 2128 | } 2129 | 2130 | static int sshfs_mknod(const char *path, mode_t mode, dev_t rdev) 2131 | { 2132 | int err; 2133 | struct buffer buf; 2134 | struct buffer handle; 2135 | (void) rdev; 2136 | 2137 | if ((mode & S_IFMT) != S_IFREG) 2138 | return -EPERM; 2139 | 2140 | buf_init(&buf, 0); 2141 | buf_add_path(&buf, path); 2142 | buf_add_uint32(&buf, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_EXCL); 2143 | buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); 2144 | buf_add_uint32(&buf, mode); 2145 | err = sftp_request(SSH_FXP_OPEN, &buf, SSH_FXP_HANDLE, &handle); 2146 | if (!err) { 2147 | int err2; 2148 | buf_finish(&handle); 2149 | err2 = sftp_request(SSH_FXP_CLOSE, &handle, SSH_FXP_STATUS, 2150 | NULL); 2151 | if (!err) 2152 | err = err2; 2153 | buf_free(&handle); 2154 | } 2155 | buf_free(&buf); 2156 | return err; 2157 | } 2158 | 2159 | static int sshfs_symlink(const char *from, const char *to) 2160 | { 2161 | int err; 2162 | struct buffer buf; 2163 | 2164 | if (sshfs.server_version < 3) 2165 | return -EPERM; 2166 | 2167 | /* openssh sftp server doesn't follow standard: link target and 2168 | link name are mixed up, so we must also be non-standard :( */ 2169 | buf_init(&buf, 0); 2170 | buf_add_string(&buf, from); 2171 | buf_add_path(&buf, to); 2172 | err = sftp_request(SSH_FXP_SYMLINK, &buf, SSH_FXP_STATUS, NULL); 2173 | buf_free(&buf); 2174 | return err; 2175 | } 2176 | 2177 | static int sshfs_unlink(const char *path) 2178 | { 2179 | int err; 2180 | struct buffer buf; 2181 | buf_init(&buf, 0); 2182 | buf_add_path(&buf, path); 2183 | err = sftp_request(SSH_FXP_REMOVE, &buf, SSH_FXP_STATUS, NULL); 2184 | buf_free(&buf); 2185 | return err; 2186 | } 2187 | 2188 | static int sshfs_rmdir(const char *path) 2189 | { 2190 | int err; 2191 | struct buffer buf; 2192 | buf_init(&buf, 0); 2193 | buf_add_path(&buf, path); 2194 | err = sftp_request(SSH_FXP_RMDIR, &buf, SSH_FXP_STATUS, NULL); 2195 | buf_free(&buf); 2196 | return err; 2197 | } 2198 | 2199 | static int sshfs_do_rename(const char *from, const char *to) 2200 | { 2201 | int err; 2202 | struct buffer buf; 2203 | buf_init(&buf, 0); 2204 | buf_add_path(&buf, from); 2205 | buf_add_path(&buf, to); 2206 | err = sftp_request(SSH_FXP_RENAME, &buf, SSH_FXP_STATUS, NULL); 2207 | buf_free(&buf); 2208 | return err; 2209 | } 2210 | 2211 | static int sshfs_ext_posix_rename(const char *from, const char *to) 2212 | { 2213 | int err; 2214 | struct buffer buf; 2215 | buf_init(&buf, 0); 2216 | buf_add_string(&buf, SFTP_EXT_POSIX_RENAME); 2217 | buf_add_path(&buf, from); 2218 | buf_add_path(&buf, to); 2219 | err = sftp_request(SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, NULL); 2220 | buf_free(&buf); 2221 | return err; 2222 | } 2223 | 2224 | static void random_string(char *str, int length) 2225 | { 2226 | int i; 2227 | for (i = 0; i < length; i++) 2228 | *str++ = (char)('0' + rand_r(&sshfs.randseed) % 10); 2229 | *str = '\0'; 2230 | } 2231 | 2232 | static int sshfs_rename(const char *from, const char *to) 2233 | { 2234 | int err; 2235 | if (sshfs.ext_posix_rename) 2236 | err = sshfs_ext_posix_rename(from, to); 2237 | else 2238 | err = sshfs_do_rename(from, to); 2239 | if (err == -EPERM && sshfs.rename_workaround) { 2240 | size_t tolen = strlen(to); 2241 | if (tolen + RENAME_TEMP_CHARS < PATH_MAX) { 2242 | int tmperr; 2243 | char totmp[PATH_MAX]; 2244 | strcpy(totmp, to); 2245 | random_string(totmp + tolen, RENAME_TEMP_CHARS); 2246 | tmperr = sshfs_do_rename(to, totmp); 2247 | if (!tmperr) { 2248 | err = sshfs_do_rename(from, to); 2249 | if (!err) 2250 | err = sshfs_unlink(totmp); 2251 | else 2252 | sshfs_do_rename(totmp, to); 2253 | } 2254 | } 2255 | } 2256 | return err; 2257 | } 2258 | 2259 | static int sshfs_link(const char *from, const char *to) 2260 | { 2261 | int err = -ENOSYS; 2262 | 2263 | if (sshfs.ext_hardlink) { 2264 | struct buffer buf; 2265 | 2266 | buf_init(&buf, 0); 2267 | buf_add_string(&buf, SFTP_EXT_HARDLINK); 2268 | buf_add_path(&buf, from); 2269 | buf_add_path(&buf, to); 2270 | err = sftp_request(SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, 2271 | NULL); 2272 | buf_free(&buf); 2273 | } 2274 | 2275 | return err; 2276 | } 2277 | 2278 | static int sshfs_chmod(const char *path, mode_t mode) 2279 | { 2280 | int err; 2281 | struct buffer buf; 2282 | buf_init(&buf, 0); 2283 | buf_add_path(&buf, path); 2284 | buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); 2285 | buf_add_uint32(&buf, mode); 2286 | /* FIXME: really needs LSETSTAT extension (debian Bug#640038) */ 2287 | err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); 2288 | buf_free(&buf); 2289 | return err; 2290 | } 2291 | 2292 | static int sshfs_chown(const char *path, uid_t uid, gid_t gid) 2293 | { 2294 | int err; 2295 | struct buffer buf; 2296 | 2297 | if (sshfs.remote_uid_detected && uid == sshfs.local_uid) 2298 | uid = sshfs.remote_uid; 2299 | if (sshfs.idmap == IDMAP_FILE && sshfs.r_uid_map) 2300 | if(translate_id(&uid, sshfs.r_uid_map) == -1) 2301 | return -EPERM; 2302 | if (sshfs.idmap == IDMAP_FILE && sshfs.r_gid_map) 2303 | if (translate_id(&gid, sshfs.r_gid_map) == -1) 2304 | return -EPERM; 2305 | 2306 | buf_init(&buf, 0); 2307 | buf_add_path(&buf, path); 2308 | buf_add_uint32(&buf, SSH_FILEXFER_ATTR_UIDGID); 2309 | #ifdef __APPLE__ 2310 | if (sshfs.remote_uid_detected) { 2311 | if (uid == sshfs.local_uid) 2312 | uid = sshfs.remote_uid; 2313 | if (gid == sshfs.local_gid) 2314 | gid = sshfs.remote_gid; 2315 | } 2316 | #endif 2317 | buf_add_uint32(&buf, uid); 2318 | buf_add_uint32(&buf, gid); 2319 | err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); 2320 | buf_free(&buf); 2321 | return err; 2322 | } 2323 | 2324 | static int sshfs_truncate_workaround(const char *path, off_t size, 2325 | struct fuse_file_info *fi); 2326 | 2327 | static void sshfs_inc_modifver(void) 2328 | { 2329 | pthread_mutex_lock(&sshfs.lock); 2330 | sshfs.modifver++; 2331 | pthread_mutex_unlock(&sshfs.lock); 2332 | } 2333 | 2334 | static int sshfs_truncate(const char *path, off_t size) 2335 | { 2336 | int err; 2337 | struct buffer buf; 2338 | 2339 | sshfs_inc_modifver(); 2340 | if (size == 0 || sshfs.truncate_workaround) 2341 | return sshfs_truncate_workaround(path, size, NULL); 2342 | 2343 | buf_init(&buf, 0); 2344 | buf_add_path(&buf, path); 2345 | buf_add_uint32(&buf, SSH_FILEXFER_ATTR_SIZE); 2346 | buf_add_uint64(&buf, size); 2347 | err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); 2348 | buf_free(&buf); 2349 | return err; 2350 | } 2351 | 2352 | static int sshfs_utime(const char *path, struct utimbuf *ubuf) 2353 | { 2354 | int err; 2355 | struct buffer buf; 2356 | buf_init(&buf, 0); 2357 | buf_add_path(&buf, path); 2358 | buf_add_uint32(&buf, SSH_FILEXFER_ATTR_ACMODTIME); 2359 | buf_add_uint32(&buf, ubuf->actime); 2360 | buf_add_uint32(&buf, ubuf->modtime); 2361 | err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); 2362 | buf_free(&buf); 2363 | return err; 2364 | } 2365 | 2366 | static inline int sshfs_file_is_conn(struct sshfs_file *sf) 2367 | { 2368 | int ret; 2369 | 2370 | pthread_mutex_lock(&sshfs.lock); 2371 | ret = (sf->connver == sshfs.connver); 2372 | pthread_mutex_unlock(&sshfs.lock); 2373 | 2374 | return ret; 2375 | } 2376 | 2377 | static int sshfs_open_common(const char *path, mode_t mode, 2378 | struct fuse_file_info *fi) 2379 | { 2380 | int err; 2381 | int err2; 2382 | struct buffer buf; 2383 | struct buffer outbuf; 2384 | struct stat stbuf; 2385 | struct sshfs_file *sf; 2386 | struct request *open_req; 2387 | uint32_t pflags = 0; 2388 | struct iovec iov; 2389 | uint8_t type; 2390 | uint64_t wrctr = cache_get_write_ctr(); 2391 | 2392 | if ((fi->flags & O_ACCMODE) == O_RDONLY) 2393 | pflags = SSH_FXF_READ; 2394 | else if((fi->flags & O_ACCMODE) == O_WRONLY) 2395 | pflags = SSH_FXF_WRITE; 2396 | else if ((fi->flags & O_ACCMODE) == O_RDWR) 2397 | pflags = SSH_FXF_READ | SSH_FXF_WRITE; 2398 | else 2399 | return -EINVAL; 2400 | 2401 | if (fi->flags & O_CREAT) 2402 | pflags |= SSH_FXF_CREAT; 2403 | 2404 | if (fi->flags & O_EXCL) 2405 | pflags |= SSH_FXF_EXCL; 2406 | 2407 | if (fi->flags & O_TRUNC) 2408 | pflags |= SSH_FXF_TRUNC; 2409 | 2410 | sf = g_new0(struct sshfs_file, 1); 2411 | list_init(&sf->write_reqs); 2412 | pthread_cond_init(&sf->write_finished, NULL); 2413 | #ifdef __APPLE__ 2414 | pthread_mutex_init(&sf->file_lock, NULL); 2415 | #endif 2416 | /* Assume random read after open */ 2417 | sf->is_seq = 0; 2418 | sf->refs = 1; 2419 | sf->next_pos = 0; 2420 | pthread_mutex_lock(&sshfs.lock); 2421 | sf->modifver= sshfs.modifver; 2422 | sf->connver = sshfs.connver; 2423 | pthread_mutex_unlock(&sshfs.lock); 2424 | buf_init(&buf, 0); 2425 | buf_add_path(&buf, path); 2426 | buf_add_uint32(&buf, pflags); 2427 | buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); 2428 | buf_add_uint32(&buf, mode); 2429 | buf_to_iov(&buf, &iov); 2430 | sftp_request_send(SSH_FXP_OPEN, &iov, 1, NULL, NULL, 1, NULL, 2431 | &open_req); 2432 | buf_clear(&buf); 2433 | buf_add_path(&buf, path); 2434 | type = sshfs.follow_symlinks ? SSH_FXP_STAT : SSH_FXP_LSTAT; 2435 | err2 = sftp_request(type, &buf, SSH_FXP_ATTRS, &outbuf); 2436 | if (!err2) { 2437 | err2 = buf_get_attrs(&outbuf, &stbuf, NULL); 2438 | buf_free(&outbuf); 2439 | } 2440 | err = sftp_request_wait(open_req, SSH_FXP_OPEN, SSH_FXP_HANDLE, 2441 | &sf->handle); 2442 | if (!err && err2) { 2443 | buf_finish(&sf->handle); 2444 | sftp_request(SSH_FXP_CLOSE, &sf->handle, 0, NULL); 2445 | buf_free(&sf->handle); 2446 | err = err2; 2447 | } 2448 | 2449 | if (!err) { 2450 | #ifdef __APPLE__ 2451 | if (cache_enabled) 2452 | cache_add_attr(path, &stbuf, wrctr); 2453 | #else 2454 | cache_add_attr(path, &stbuf, wrctr); 2455 | #endif 2456 | buf_finish(&sf->handle); 2457 | fi->fh = (unsigned long) sf; 2458 | } else { 2459 | #ifdef __APPLE__ 2460 | if (cache_enabled) 2461 | cache_invalidate(path); 2462 | #else 2463 | cache_invalidate(path); 2464 | #endif 2465 | g_free(sf); 2466 | } 2467 | buf_free(&buf); 2468 | return err; 2469 | } 2470 | 2471 | static int sshfs_open(const char *path, struct fuse_file_info *fi) 2472 | { 2473 | return sshfs_open_common(path, 0, fi); 2474 | } 2475 | 2476 | static inline struct sshfs_file *get_sshfs_file(struct fuse_file_info *fi) 2477 | { 2478 | return (struct sshfs_file *) (uintptr_t) fi->fh; 2479 | } 2480 | 2481 | static int sshfs_flush(const char *path, struct fuse_file_info *fi) 2482 | { 2483 | int err; 2484 | struct sshfs_file *sf = get_sshfs_file(fi); 2485 | struct list_head write_reqs; 2486 | struct list_head *curr_list; 2487 | 2488 | if (!sshfs_file_is_conn(sf)) 2489 | return -EIO; 2490 | 2491 | if (sshfs.sync_write) 2492 | return 0; 2493 | 2494 | (void) path; 2495 | pthread_mutex_lock(&sshfs.lock); 2496 | if (!list_empty(&sf->write_reqs)) { 2497 | curr_list = sf->write_reqs.prev; 2498 | list_del(&sf->write_reqs); 2499 | list_init(&sf->write_reqs); 2500 | list_add(&write_reqs, curr_list); 2501 | while (!list_empty(&write_reqs)) 2502 | pthread_cond_wait(&sf->write_finished, &sshfs.lock); 2503 | } 2504 | err = sf->write_error; 2505 | sf->write_error = 0; 2506 | pthread_mutex_unlock(&sshfs.lock); 2507 | return err; 2508 | } 2509 | 2510 | static int sshfs_fsync(const char *path, int isdatasync, 2511 | struct fuse_file_info *fi) 2512 | { 2513 | (void) isdatasync; 2514 | return sshfs_flush(path, fi); 2515 | } 2516 | 2517 | static void sshfs_file_put(struct sshfs_file *sf) 2518 | { 2519 | #ifdef __APPLE__ 2520 | pthread_mutex_lock(&sf->file_lock); 2521 | #endif 2522 | sf->refs--; 2523 | #ifdef __APPLE__ 2524 | if (!sf->refs) { 2525 | pthread_mutex_unlock(&sf->file_lock); 2526 | g_free(sf); 2527 | } else { 2528 | pthread_mutex_unlock(&sf->file_lock); 2529 | } 2530 | #else 2531 | if (!sf->refs) 2532 | g_free(sf); 2533 | #endif 2534 | } 2535 | 2536 | static void sshfs_file_get(struct sshfs_file *sf) 2537 | { 2538 | #ifdef __APPLE__ 2539 | pthread_mutex_lock(&sf->file_lock); 2540 | #endif 2541 | sf->refs++; 2542 | #ifdef __APPLE__ 2543 | pthread_mutex_unlock(&sf->file_lock); 2544 | #endif 2545 | } 2546 | 2547 | static int sshfs_release(const char *path, struct fuse_file_info *fi) 2548 | { 2549 | struct sshfs_file *sf = get_sshfs_file(fi); 2550 | struct buffer *handle = &sf->handle; 2551 | if (sshfs_file_is_conn(sf)) { 2552 | sshfs_flush(path, fi); 2553 | sftp_request(SSH_FXP_CLOSE, handle, 0, NULL); 2554 | } 2555 | buf_free(handle); 2556 | chunk_put_locked(sf->readahead); 2557 | sshfs_file_put(sf); 2558 | return 0; 2559 | } 2560 | 2561 | static void sshfs_read_end(struct request *req) 2562 | { 2563 | struct read_req *rreq = (struct read_req *) req->data; 2564 | if (req->error) 2565 | rreq->res = req->error; 2566 | else if (req->replied) { 2567 | rreq->res = -EIO; 2568 | 2569 | if (req->reply_type == SSH_FXP_STATUS) { 2570 | uint32_t serr; 2571 | if (buf_get_uint32(&req->reply, &serr) != -1) { 2572 | if (serr == SSH_FX_EOF) 2573 | rreq->res = 0; 2574 | else 2575 | rreq->res = -sftp_error_to_errno(serr); 2576 | } 2577 | } else if (req->reply_type == SSH_FXP_DATA) { 2578 | uint32_t retsize; 2579 | if (buf_get_uint32(&req->reply, &retsize) != -1) { 2580 | if (retsize > rreq->size) { 2581 | fprintf(stderr, "long read\n"); 2582 | } else if (buf_check_get(&req->reply, retsize) != -1) { 2583 | rreq->res = retsize; 2584 | rreq->data = req->reply; 2585 | buf_init(&req->reply, 0); 2586 | } 2587 | } 2588 | } else { 2589 | fprintf(stderr, "protocol error\n"); 2590 | } 2591 | } else { 2592 | rreq->res = -EIO; 2593 | } 2594 | 2595 | rreq->sio->num_reqs--; 2596 | if (!rreq->sio->num_reqs) 2597 | pthread_cond_broadcast(&rreq->sio->finished); 2598 | } 2599 | 2600 | static void sshfs_read_begin(struct request *req) 2601 | { 2602 | struct read_req *rreq = (struct read_req *) req->data; 2603 | rreq->sio->num_reqs++; 2604 | } 2605 | 2606 | static struct read_chunk *sshfs_send_read(struct sshfs_file *sf, size_t size, 2607 | off_t offset) 2608 | { 2609 | struct read_chunk *chunk = g_new0(struct read_chunk, 1); 2610 | struct buffer *handle = &sf->handle; 2611 | 2612 | pthread_cond_init(&chunk->sio.finished, NULL); 2613 | list_init(&chunk->reqs); 2614 | chunk->size = size; 2615 | chunk->offset = offset; 2616 | chunk->refs = 1; 2617 | 2618 | while (size) { 2619 | int err; 2620 | struct buffer buf; 2621 | struct iovec iov[1]; 2622 | struct read_req *rreq; 2623 | size_t bsize = size < sshfs.max_read ? size : sshfs.max_read; 2624 | 2625 | rreq = g_new0(struct read_req, 1); 2626 | rreq->sio = &chunk->sio; 2627 | rreq->size = bsize; 2628 | buf_init(&rreq->data, 0); 2629 | list_add(&rreq->list, &chunk->reqs); 2630 | 2631 | buf_init(&buf, 0); 2632 | buf_add_buf(&buf, handle); 2633 | buf_add_uint64(&buf, offset); 2634 | buf_add_uint32(&buf, bsize); 2635 | buf_to_iov(&buf, &iov[0]); 2636 | err = sftp_request_send(SSH_FXP_READ, iov, 1, 2637 | sshfs_read_begin, 2638 | sshfs_read_end, 2639 | 0, rreq, NULL); 2640 | 2641 | buf_free(&buf); 2642 | if (err) 2643 | break; 2644 | 2645 | size -= bsize; 2646 | offset += bsize; 2647 | } 2648 | 2649 | return chunk; 2650 | } 2651 | 2652 | static int wait_chunk(struct read_chunk *chunk, char *buf, size_t size) 2653 | { 2654 | int res = 0; 2655 | struct read_req *rreq; 2656 | 2657 | pthread_mutex_lock(&sshfs.lock); 2658 | while (chunk->sio.num_reqs) 2659 | pthread_cond_wait(&chunk->sio.finished, &sshfs.lock); 2660 | pthread_mutex_unlock(&sshfs.lock); 2661 | 2662 | 2663 | if (chunk->sio.error) { 2664 | if (chunk->sio.error != MY_EOF) 2665 | res = chunk->sio.error; 2666 | 2667 | goto out; 2668 | } 2669 | 2670 | while (!list_empty(&chunk->reqs) && size) { 2671 | rreq = list_entry(chunk->reqs.prev, struct read_req, list); 2672 | 2673 | if (rreq->res < 0) { 2674 | chunk->sio.error = rreq->res; 2675 | break; 2676 | } if (rreq->res == 0) { 2677 | chunk->sio.error = MY_EOF; 2678 | break; 2679 | } else if (size < (size_t) rreq->res) { 2680 | buf_get_mem(&rreq->data, buf, size); 2681 | rreq->res -= size; 2682 | rreq->size -= size; 2683 | res += size; 2684 | break; 2685 | } else { 2686 | buf_get_mem(&rreq->data, buf, rreq->res); 2687 | res += rreq->res; 2688 | if ((size_t) rreq->res < rreq->size) { 2689 | chunk->sio.error = MY_EOF; 2690 | break; 2691 | } 2692 | buf += rreq->res; 2693 | size -= rreq->res; 2694 | list_del(&rreq->list); 2695 | buf_free(&rreq->data); 2696 | g_free(rreq); 2697 | } 2698 | } 2699 | 2700 | if (res > 0) { 2701 | chunk->offset += res; 2702 | chunk->size -= res; 2703 | } 2704 | 2705 | out: 2706 | chunk_put_locked(chunk); 2707 | return res; 2708 | } 2709 | 2710 | static int sshfs_sync_read(struct sshfs_file *sf, char *buf, size_t size, 2711 | off_t offset) 2712 | { 2713 | struct read_chunk *chunk; 2714 | 2715 | chunk = sshfs_send_read(sf, size, offset); 2716 | return wait_chunk(chunk, buf, size); 2717 | } 2718 | 2719 | static void submit_read(struct sshfs_file *sf, size_t size, off_t offset, 2720 | struct read_chunk **chunkp) 2721 | { 2722 | struct read_chunk *chunk; 2723 | 2724 | chunk = sshfs_send_read(sf, size, offset); 2725 | pthread_mutex_lock(&sshfs.lock); 2726 | chunk->modifver = sshfs.modifver; 2727 | chunk_put(*chunkp); 2728 | *chunkp = chunk; 2729 | chunk->refs++; 2730 | pthread_mutex_unlock(&sshfs.lock); 2731 | } 2732 | 2733 | static struct read_chunk *search_read_chunk(struct sshfs_file *sf, off_t offset) 2734 | { 2735 | struct read_chunk *ch = sf->readahead; 2736 | if (ch && ch->offset == offset && ch->modifver == sshfs.modifver) { 2737 | ch->refs++; 2738 | return ch; 2739 | } else 2740 | return NULL; 2741 | } 2742 | 2743 | static int sshfs_async_read(struct sshfs_file *sf, char *rbuf, size_t size, 2744 | off_t offset) 2745 | { 2746 | int res = 0; 2747 | size_t total = 0; 2748 | struct read_chunk *chunk; 2749 | struct read_chunk *chunk_prev = NULL; 2750 | size_t origsize = size; 2751 | int curr_is_seq; 2752 | 2753 | pthread_mutex_lock(&sshfs.lock); 2754 | curr_is_seq = sf->is_seq; 2755 | sf->is_seq = (sf->next_pos == offset && sf->modifver == sshfs.modifver); 2756 | sf->next_pos = offset + size; 2757 | sf->modifver = sshfs.modifver; 2758 | chunk = search_read_chunk(sf, offset); 2759 | pthread_mutex_unlock(&sshfs.lock); 2760 | 2761 | if (chunk && chunk->size < size) { 2762 | chunk_prev = chunk; 2763 | size -= chunk->size; 2764 | offset += chunk->size; 2765 | chunk = NULL; 2766 | } 2767 | 2768 | if (!chunk) 2769 | submit_read(sf, size, offset, &chunk); 2770 | 2771 | if (curr_is_seq && chunk && chunk->size <= size) 2772 | submit_read(sf, origsize, offset + size, &sf->readahead); 2773 | 2774 | if (chunk_prev) { 2775 | size_t prev_size = chunk_prev->size; 2776 | res = wait_chunk(chunk_prev, rbuf, prev_size); 2777 | if (res < (int) prev_size) { 2778 | chunk_put_locked(chunk); 2779 | return res; 2780 | } 2781 | rbuf += res; 2782 | total += res; 2783 | } 2784 | res = wait_chunk(chunk, rbuf, size); 2785 | if (res > 0) 2786 | total += res; 2787 | if (res < 0) 2788 | return res; 2789 | 2790 | return total; 2791 | } 2792 | 2793 | static int sshfs_read(const char *path, char *rbuf, size_t size, off_t offset, 2794 | struct fuse_file_info *fi) 2795 | { 2796 | struct sshfs_file *sf = get_sshfs_file(fi); 2797 | (void) path; 2798 | 2799 | if (!sshfs_file_is_conn(sf)) 2800 | return -EIO; 2801 | 2802 | if (sshfs.sync_read) 2803 | return sshfs_sync_read(sf, rbuf, size, offset); 2804 | else 2805 | return sshfs_async_read(sf, rbuf, size, offset); 2806 | } 2807 | 2808 | static void sshfs_write_begin(struct request *req) 2809 | { 2810 | struct sshfs_file *sf = (struct sshfs_file *) req->data; 2811 | 2812 | sshfs_file_get(sf); 2813 | list_add(&req->list, &sf->write_reqs); 2814 | } 2815 | 2816 | static void sshfs_write_end(struct request *req) 2817 | { 2818 | uint32_t serr; 2819 | struct sshfs_file *sf = (struct sshfs_file *) req->data; 2820 | 2821 | if (req->error) 2822 | sf->write_error = req->error; 2823 | else if (req->replied) { 2824 | if (req->reply_type != SSH_FXP_STATUS) { 2825 | fprintf(stderr, "protocol error\n"); 2826 | } else if (buf_get_uint32(&req->reply, &serr) != -1 && 2827 | serr != SSH_FX_OK) { 2828 | sf->write_error = -EIO; 2829 | } 2830 | } 2831 | list_del(&req->list); 2832 | pthread_cond_broadcast(&sf->write_finished); 2833 | sshfs_file_put(sf); 2834 | } 2835 | 2836 | static int sshfs_async_write(struct sshfs_file *sf, const char *wbuf, 2837 | size_t size, off_t offset) 2838 | { 2839 | int err = 0; 2840 | struct buffer *handle = &sf->handle; 2841 | 2842 | while (!err && size) { 2843 | struct buffer buf; 2844 | struct iovec iov[2]; 2845 | size_t bsize = size < sshfs.max_write ? size : sshfs.max_write; 2846 | 2847 | buf_init(&buf, 0); 2848 | buf_add_buf(&buf, handle); 2849 | buf_add_uint64(&buf, offset); 2850 | buf_add_uint32(&buf, bsize); 2851 | buf_to_iov(&buf, &iov[0]); 2852 | iov[1].iov_base = (void *) wbuf; 2853 | iov[1].iov_len = bsize; 2854 | err = sftp_request_send(SSH_FXP_WRITE, iov, 2, 2855 | sshfs_write_begin, sshfs_write_end, 2856 | 0, sf, NULL); 2857 | buf_free(&buf); 2858 | size -= bsize; 2859 | wbuf += bsize; 2860 | offset += bsize; 2861 | } 2862 | 2863 | return err; 2864 | } 2865 | 2866 | static void sshfs_sync_write_begin(struct request *req) 2867 | { 2868 | struct sshfs_io *sio = (struct sshfs_io *) req->data; 2869 | sio->num_reqs++; 2870 | } 2871 | 2872 | static void sshfs_sync_write_end(struct request *req) 2873 | { 2874 | uint32_t serr; 2875 | struct sshfs_io *sio = (struct sshfs_io *) req->data; 2876 | 2877 | if (req->error) { 2878 | sio->error = req->error; 2879 | } else if (req->replied) { 2880 | if (req->reply_type != SSH_FXP_STATUS) { 2881 | fprintf(stderr, "protocol error\n"); 2882 | } else if (buf_get_uint32(&req->reply, &serr) != -1 && 2883 | serr != SSH_FX_OK) { 2884 | sio->error = -EIO; 2885 | } 2886 | } 2887 | sio->num_reqs--; 2888 | if (!sio->num_reqs) 2889 | pthread_cond_broadcast(&sio->finished); 2890 | } 2891 | 2892 | 2893 | static int sshfs_sync_write(struct sshfs_file *sf, const char *wbuf, 2894 | size_t size, off_t offset) 2895 | { 2896 | int err = 0; 2897 | struct buffer *handle = &sf->handle; 2898 | struct sshfs_io sio = { .error = 0, .num_reqs = 0 }; 2899 | 2900 | pthread_cond_init(&sio.finished, NULL); 2901 | 2902 | while (!err && size) { 2903 | struct buffer buf; 2904 | struct iovec iov[2]; 2905 | size_t bsize = size < sshfs.max_write ? size : sshfs.max_write; 2906 | 2907 | buf_init(&buf, 0); 2908 | buf_add_buf(&buf, handle); 2909 | buf_add_uint64(&buf, offset); 2910 | buf_add_uint32(&buf, bsize); 2911 | buf_to_iov(&buf, &iov[0]); 2912 | iov[1].iov_base = (void *) wbuf; 2913 | iov[1].iov_len = bsize; 2914 | err = sftp_request_send(SSH_FXP_WRITE, iov, 2, 2915 | sshfs_sync_write_begin, 2916 | sshfs_sync_write_end, 2917 | 0, &sio, NULL); 2918 | buf_free(&buf); 2919 | size -= bsize; 2920 | wbuf += bsize; 2921 | offset += bsize; 2922 | } 2923 | 2924 | pthread_mutex_lock(&sshfs.lock); 2925 | while (sio.num_reqs) 2926 | pthread_cond_wait(&sio.finished, &sshfs.lock); 2927 | pthread_mutex_unlock(&sshfs.lock); 2928 | 2929 | if (!err) 2930 | err = sio.error; 2931 | 2932 | return err; 2933 | } 2934 | 2935 | static int sshfs_write(const char *path, const char *wbuf, size_t size, 2936 | off_t offset, struct fuse_file_info *fi) 2937 | { 2938 | int err; 2939 | struct sshfs_file *sf = get_sshfs_file(fi); 2940 | 2941 | (void) path; 2942 | 2943 | if (!sshfs_file_is_conn(sf)) 2944 | return -EIO; 2945 | 2946 | sshfs_inc_modifver(); 2947 | 2948 | if (!sshfs.sync_write && !sf->write_error) 2949 | err = sshfs_async_write(sf, wbuf, size, offset); 2950 | else 2951 | err = sshfs_sync_write(sf, wbuf, size, offset); 2952 | 2953 | return err ? err : (int) size; 2954 | } 2955 | 2956 | static int sshfs_ext_statvfs(const char *path, struct statvfs *stbuf) 2957 | { 2958 | int err; 2959 | struct buffer buf; 2960 | struct buffer outbuf; 2961 | buf_init(&buf, 0); 2962 | buf_add_string(&buf, SFTP_EXT_STATVFS); 2963 | buf_add_path(&buf, path); 2964 | err = sftp_request(SSH_FXP_EXTENDED, &buf, SSH_FXP_EXTENDED_REPLY, 2965 | &outbuf); 2966 | if (!err) { 2967 | if (buf_get_statvfs(&outbuf, stbuf) == -1) 2968 | err = -EIO; 2969 | buf_free(&outbuf); 2970 | } 2971 | buf_free(&buf); 2972 | return err; 2973 | } 2974 | 2975 | 2976 | #if FUSE_VERSION >= 25 2977 | static int sshfs_statfs(const char *path, struct statvfs *buf) 2978 | { 2979 | if (sshfs.ext_statvfs) 2980 | return sshfs_ext_statvfs(path, buf); 2981 | 2982 | buf->f_namemax = 255; 2983 | buf->f_bsize = sshfs.blksize; 2984 | /* 2985 | * df seems to use f_bsize instead of f_frsize, so make them 2986 | * the same 2987 | */ 2988 | buf->f_frsize = buf->f_bsize; 2989 | buf->f_blocks = buf->f_bfree = buf->f_bavail = 2990 | 1000ULL * 1024 * 1024 * 1024 / buf->f_frsize; 2991 | buf->f_files = buf->f_ffree = 1000000000; 2992 | return 0; 2993 | } 2994 | #else 2995 | static int sshfs_statfs(const char *path, struct statfs *buf) 2996 | { 2997 | if (sshfs.ext_statvfs) { 2998 | int err; 2999 | struct statvfs vbuf; 3000 | 3001 | err = sshfs_ext_statvfs(path, &vbuf); 3002 | if (!err) { 3003 | buf->f_bsize = vbuf.f_bsize; 3004 | buf->f_blocks = vbuf.f_blocks; 3005 | buf->f_bfree = vbuf.f_bfree; 3006 | buf->f_bavail = vbuf.f_bavail; 3007 | buf->f_files = vbuf.f_files; 3008 | buf->f_ffree = vbuf.f_ffree; 3009 | buf->f_namelen = vbuf.f_namemax; 3010 | } 3011 | return err; 3012 | } 3013 | 3014 | buf->f_namelen = 255; 3015 | buf->f_bsize = sshfs.blksize; 3016 | buf->f_blocks = buf->f_bfree = buf->f_bavail = 3017 | 1000ULL * 1024 * 1024 * 1024 / buf->f_bsize; 3018 | buf->f_files = buf->f_ffree = 1000000000; 3019 | return 0; 3020 | } 3021 | #endif 3022 | 3023 | #if FUSE_VERSION >= 25 3024 | static int sshfs_create(const char *path, mode_t mode, 3025 | struct fuse_file_info *fi) 3026 | { 3027 | return sshfs_open_common(path, mode, fi); 3028 | } 3029 | 3030 | static int sshfs_ftruncate(const char *path, off_t size, 3031 | struct fuse_file_info *fi) 3032 | { 3033 | int err; 3034 | struct buffer buf; 3035 | struct sshfs_file *sf = get_sshfs_file(fi); 3036 | 3037 | (void) path; 3038 | 3039 | if (!sshfs_file_is_conn(sf)) 3040 | return -EIO; 3041 | 3042 | sshfs_inc_modifver(); 3043 | if (sshfs.truncate_workaround) 3044 | return sshfs_truncate_workaround(path, size, fi); 3045 | 3046 | buf_init(&buf, 0); 3047 | buf_add_buf(&buf, &sf->handle); 3048 | buf_add_uint32(&buf, SSH_FILEXFER_ATTR_SIZE); 3049 | buf_add_uint64(&buf, size); 3050 | err = sftp_request(SSH_FXP_FSETSTAT, &buf, SSH_FXP_STATUS, NULL); 3051 | buf_free(&buf); 3052 | 3053 | return err; 3054 | } 3055 | #endif 3056 | 3057 | static int sshfs_fgetattr(const char *path, struct stat *stbuf, 3058 | struct fuse_file_info *fi) 3059 | { 3060 | int err; 3061 | struct buffer buf; 3062 | struct buffer outbuf; 3063 | struct sshfs_file *sf = get_sshfs_file(fi); 3064 | 3065 | (void) path; 3066 | 3067 | if (!sshfs_file_is_conn(sf)) 3068 | return -EIO; 3069 | 3070 | if (sshfs.fstat_workaround) 3071 | return sshfs_getattr(path, stbuf); 3072 | 3073 | buf_init(&buf, 0); 3074 | buf_add_buf(&buf, &sf->handle); 3075 | err = sftp_request(SSH_FXP_FSTAT, &buf, SSH_FXP_ATTRS, &outbuf); 3076 | if (!err) { 3077 | err = buf_get_attrs(&outbuf, stbuf, NULL); 3078 | buf_free(&outbuf); 3079 | } 3080 | buf_free(&buf); 3081 | return err; 3082 | } 3083 | 3084 | static int sshfs_truncate_zero(const char *path) 3085 | { 3086 | int err; 3087 | struct fuse_file_info fi; 3088 | 3089 | fi.flags = O_WRONLY | O_TRUNC; 3090 | err = sshfs_open(path, &fi); 3091 | if (!err) 3092 | sshfs_release(path, &fi); 3093 | 3094 | return err; 3095 | } 3096 | 3097 | static size_t calc_buf_size(off_t size, off_t offset) 3098 | { 3099 | return offset + sshfs.max_read < size ? sshfs.max_read : size - offset; 3100 | } 3101 | 3102 | static int sshfs_truncate_shrink(const char *path, off_t size) 3103 | { 3104 | int res; 3105 | char *data; 3106 | off_t offset; 3107 | struct fuse_file_info fi; 3108 | 3109 | data = calloc(size, 1); 3110 | if (!data) 3111 | return -ENOMEM; 3112 | 3113 | fi.flags = O_RDONLY; 3114 | res = sshfs_open(path, &fi); 3115 | if (res) 3116 | goto out; 3117 | 3118 | for (offset = 0; offset < size; offset += res) { 3119 | size_t bufsize = calc_buf_size(size, offset); 3120 | res = sshfs_read(path, data + offset, bufsize, offset, &fi); 3121 | if (res <= 0) 3122 | break; 3123 | } 3124 | sshfs_release(path, &fi); 3125 | if (res < 0) 3126 | goto out; 3127 | 3128 | fi.flags = O_WRONLY | O_TRUNC; 3129 | res = sshfs_open(path, &fi); 3130 | if (res) 3131 | goto out; 3132 | 3133 | for (offset = 0; offset < size; offset += res) { 3134 | size_t bufsize = calc_buf_size(size, offset); 3135 | res = sshfs_write(path, data + offset, bufsize, offset, &fi); 3136 | if (res < 0) 3137 | break; 3138 | } 3139 | if (res >= 0) 3140 | res = sshfs_flush(path, &fi); 3141 | sshfs_release(path, &fi); 3142 | 3143 | out: 3144 | free(data); 3145 | return res; 3146 | } 3147 | 3148 | static int sshfs_truncate_extend(const char *path, off_t size, 3149 | struct fuse_file_info *fi) 3150 | { 3151 | int res; 3152 | char c = 0; 3153 | struct fuse_file_info tmpfi; 3154 | struct fuse_file_info *openfi = fi; 3155 | if (!fi) { 3156 | openfi = &tmpfi; 3157 | openfi->flags = O_WRONLY; 3158 | res = sshfs_open(path, openfi); 3159 | if (res) 3160 | return res; 3161 | } 3162 | res = sshfs_write(path, &c, 1, size - 1, openfi); 3163 | if (res == 1) 3164 | res = sshfs_flush(path, openfi); 3165 | if (!fi) 3166 | sshfs_release(path, openfi); 3167 | 3168 | return res; 3169 | } 3170 | 3171 | /* 3172 | * Work around broken sftp servers which don't handle 3173 | * SSH_FILEXFER_ATTR_SIZE in SETSTAT request. 3174 | * 3175 | * If new size is zero, just open the file with O_TRUNC. 3176 | * 3177 | * If new size is smaller than current size, then copy file locally, 3178 | * then open/trunc and send it back. 3179 | * 3180 | * If new size is greater than current size, then write a zero byte to 3181 | * the new end of the file. 3182 | */ 3183 | static int sshfs_truncate_workaround(const char *path, off_t size, 3184 | struct fuse_file_info *fi) 3185 | { 3186 | if (size == 0) 3187 | return sshfs_truncate_zero(path); 3188 | else { 3189 | struct stat stbuf; 3190 | int err; 3191 | if (fi) 3192 | err = sshfs_fgetattr(path, &stbuf, fi); 3193 | else 3194 | err = sshfs_getattr(path, &stbuf); 3195 | if (err) 3196 | return err; 3197 | if (stbuf.st_size == size) 3198 | return 0; 3199 | else if (stbuf.st_size > size) 3200 | return sshfs_truncate_shrink(path, size); 3201 | else 3202 | return sshfs_truncate_extend(path, size, fi); 3203 | } 3204 | } 3205 | 3206 | static int processing_init(void) 3207 | { 3208 | signal(SIGPIPE, SIG_IGN); 3209 | 3210 | pthread_mutex_init(&sshfs.lock, NULL); 3211 | pthread_mutex_init(&sshfs.lock_write, NULL); 3212 | pthread_cond_init(&sshfs.outstanding_cond, NULL); 3213 | sshfs.reqtab = g_hash_table_new(NULL, NULL); 3214 | if (!sshfs.reqtab) { 3215 | fprintf(stderr, "failed to create hash table\n"); 3216 | return -1; 3217 | } 3218 | return 0; 3219 | } 3220 | 3221 | static struct fuse_cache_operations sshfs_oper = { 3222 | .oper = { 3223 | .init = sshfs_init, 3224 | .getattr = sshfs_getattr, 3225 | .readlink = sshfs_readlink, 3226 | .mknod = sshfs_mknod, 3227 | .mkdir = sshfs_mkdir, 3228 | .symlink = sshfs_symlink, 3229 | .unlink = sshfs_unlink, 3230 | .rmdir = sshfs_rmdir, 3231 | .rename = sshfs_rename, 3232 | .link = sshfs_link, 3233 | .chmod = sshfs_chmod, 3234 | .chown = sshfs_chown, 3235 | .truncate = sshfs_truncate, 3236 | .utime = sshfs_utime, 3237 | .open = sshfs_open, 3238 | .flush = sshfs_flush, 3239 | .fsync = sshfs_fsync, 3240 | .release = sshfs_release, 3241 | .read = sshfs_read, 3242 | .write = sshfs_write, 3243 | .statfs = sshfs_statfs, 3244 | #if FUSE_VERSION >= 25 3245 | .create = sshfs_create, 3246 | .ftruncate = sshfs_ftruncate, 3247 | .fgetattr = sshfs_fgetattr, 3248 | #endif 3249 | }, 3250 | .cache_getdir = sshfs_getdir, 3251 | }; 3252 | 3253 | static void usage(const char *progname) 3254 | { 3255 | printf( 3256 | "usage: %s [user@]host:[dir] mountpoint [options]\n" 3257 | "\n" 3258 | "general options:\n" 3259 | " -o opt,[opt...] mount options\n" 3260 | " -h --help print help\n" 3261 | " -V --version print version\n" 3262 | "\n" 3263 | "SSHFS options:\n" 3264 | " -p PORT equivalent to '-o port=PORT'\n" 3265 | " -C equivalent to '-o compression=yes'\n" 3266 | " -F ssh_configfile specifies alternative ssh configuration file\n" 3267 | " -1 equivalent to '-o ssh_protocol=1'\n" 3268 | " -o reconnect reconnect to server\n" 3269 | " -o delay_connect delay connection to server\n" 3270 | " -o sshfs_sync synchronous writes\n" 3271 | " -o no_readahead synchronous reads (no speculative readahead)\n" 3272 | " -o sshfs_debug print some debugging information\n" 3273 | " -o cache=BOOL enable caching {yes,no} (default: yes)\n" 3274 | " -o cache_timeout=N sets timeout for caches in seconds (default: 20)\n" 3275 | " -o cache_X_timeout=N sets timeout for {stat,dir,link} cache\n" 3276 | " -o workaround=LIST colon separated list of workarounds\n" 3277 | " none no workarounds enabled\n" 3278 | " all all workarounds enabled\n" 3279 | " [no]rename fix renaming to existing file (default: off)\n" 3280 | #ifdef SSH_NODELAY_WORKAROUND 3281 | " [no]nodelay set nodelay tcp flag in ssh (default: on)\n" 3282 | #endif 3283 | " [no]nodelaysrv set nodelay tcp flag in sshd (default: off)\n" 3284 | " [no]truncate fix truncate for old servers (default: off)\n" 3285 | " [no]buflimit fix buffer fillup bug in server (default: on)\n" 3286 | " -o idmap=TYPE user/group ID mapping, possible types are:\n" 3287 | " none no translation of the ID space (default)\n" 3288 | " user only translate UID of connecting user\n" 3289 | " file translate UIDs/GIDs contained in uidfile/gidfile\n" 3290 | " -o uidfile=FILE file containing username:remote_uid mappings\n" 3291 | " -o gidfile=FILE file containing groupname:remote_gid mappings\n" 3292 | " -o nomap=TYPE with idmap=file, how to handle missing mappings\n" 3293 | " ignore don't do any re-mapping\n" 3294 | " error return an error (default)\n" 3295 | " -o ssh_command=CMD execute CMD instead of 'ssh'\n" 3296 | " -o ssh_protocol=N ssh protocol to use (default: 2)\n" 3297 | " -o sftp_server=SERV path to sftp server or subsystem (default: sftp)\n" 3298 | " -o directport=PORT directly connect to PORT bypassing ssh\n" 3299 | " -o slave communicate over stdin and stdout bypassing network\n" 3300 | " -o transform_symlinks transform absolute symlinks to relative\n" 3301 | " -o follow_symlinks follow symlinks on the server\n" 3302 | " -o no_check_root don't check for existence of 'dir' on server\n" 3303 | " -o password_stdin read password from stdin (only for pam_mount!)\n" 3304 | " -o SSHOPT=VAL ssh options (see man ssh_config)\n" 3305 | "\n", progname); 3306 | } 3307 | 3308 | static int is_ssh_opt(const char *arg) 3309 | { 3310 | if (arg[0] != '-') { 3311 | unsigned arglen = strlen(arg); 3312 | const char **o; 3313 | for (o = ssh_opts; *o; o++) { 3314 | unsigned olen = strlen(*o); 3315 | if (arglen > olen && arg[olen] == '=' && 3316 | strncasecmp(arg, *o, olen) == 0) 3317 | return 1; 3318 | } 3319 | } 3320 | return 0; 3321 | } 3322 | 3323 | static int sshfs_fuse_main(struct fuse_args *args) 3324 | { 3325 | #if FUSE_VERSION >= 26 3326 | return fuse_main(args->argc, args->argv, cache_init(&sshfs_oper), NULL); 3327 | #else 3328 | return fuse_main(args->argc, args->argv, cache_init(&sshfs_oper)); 3329 | #endif 3330 | } 3331 | 3332 | static int sshfs_opt_proc(void *data, const char *arg, int key, 3333 | struct fuse_args *outargs) 3334 | { 3335 | char *tmp; 3336 | (void) data; 3337 | 3338 | switch (key) { 3339 | case FUSE_OPT_KEY_OPT: 3340 | if (is_ssh_opt(arg)) { 3341 | tmp = g_strdup_printf("-o%s", arg); 3342 | ssh_add_arg(tmp); 3343 | g_free(tmp); 3344 | return 0; 3345 | } 3346 | return 1; 3347 | 3348 | case FUSE_OPT_KEY_NONOPT: 3349 | if (!sshfs.host && strchr(arg, ':')) { 3350 | sshfs.host = strdup(arg); 3351 | return 0; 3352 | } 3353 | return 1; 3354 | 3355 | case KEY_PORT: 3356 | tmp = g_strdup_printf("-oPort=%s", arg + 2); 3357 | ssh_add_arg(tmp); 3358 | g_free(tmp); 3359 | return 0; 3360 | 3361 | case KEY_COMPRESS: 3362 | ssh_add_arg("-oCompression=yes"); 3363 | return 0; 3364 | 3365 | case KEY_CONFIGFILE: 3366 | tmp = g_strdup_printf("-F%s", arg + 2); 3367 | ssh_add_arg(tmp); 3368 | g_free(tmp); 3369 | return 0; 3370 | 3371 | case KEY_HELP: 3372 | usage(outargs->argv[0]); 3373 | fuse_opt_add_arg(outargs, "-ho"); 3374 | sshfs_fuse_main(outargs); 3375 | exit(1); 3376 | 3377 | case KEY_VERSION: 3378 | printf("SSHFS version %s\n", PACKAGE_VERSION); 3379 | #if FUSE_VERSION >= 25 3380 | fuse_opt_add_arg(outargs, "--version"); 3381 | sshfs_fuse_main(outargs); 3382 | #endif 3383 | exit(0); 3384 | 3385 | case KEY_FOREGROUND: 3386 | sshfs.foreground = 1; 3387 | return 1; 3388 | 3389 | default: 3390 | fprintf(stderr, "internal error\n"); 3391 | abort(); 3392 | } 3393 | } 3394 | 3395 | static int workaround_opt_proc(void *data, const char *arg, int key, 3396 | struct fuse_args *outargs) 3397 | { 3398 | (void) data; (void) key; (void) outargs; 3399 | fprintf(stderr, "unknown workaround: '%s'\n", arg); 3400 | return -1; 3401 | } 3402 | 3403 | int parse_workarounds(void) 3404 | { 3405 | int res; 3406 | char *argv[] = { "", "-o", sshfs.workarounds, NULL }; 3407 | struct fuse_args args = FUSE_ARGS_INIT(3, argv); 3408 | char *s = sshfs.workarounds; 3409 | if (!s) 3410 | return 0; 3411 | 3412 | while ((s = strchr(s, ':'))) 3413 | *s = ','; 3414 | 3415 | res = fuse_opt_parse(&args, &sshfs, workaround_opts, 3416 | workaround_opt_proc); 3417 | fuse_opt_free_args(&args); 3418 | 3419 | return res; 3420 | } 3421 | 3422 | #if FUSE_VERSION == 25 3423 | static int fuse_opt_insert_arg(struct fuse_args *args, int pos, 3424 | const char *arg) 3425 | { 3426 | assert(pos <= args->argc); 3427 | if (fuse_opt_add_arg(args, arg) == -1) 3428 | return -1; 3429 | 3430 | if (pos != args->argc - 1) { 3431 | char *newarg = args->argv[args->argc - 1]; 3432 | memmove(&args->argv[pos + 1], &args->argv[pos], 3433 | sizeof(char *) * (args->argc - pos - 1)); 3434 | args->argv[pos] = newarg; 3435 | } 3436 | return 0; 3437 | } 3438 | #endif 3439 | 3440 | static void check_large_read(struct fuse_args *args) 3441 | { 3442 | struct utsname buf; 3443 | int err = uname(&buf); 3444 | if (!err && strcmp(buf.sysname, "Linux") == 0 && 3445 | strncmp(buf.release, "2.4.", 4) == 0) 3446 | fuse_opt_insert_arg(args, 1, "-olarge_read"); 3447 | } 3448 | 3449 | static int read_password(void) 3450 | { 3451 | int size = getpagesize(); 3452 | int max_password = 64; 3453 | int n; 3454 | 3455 | sshfs.password = mmap(NULL, size, PROT_READ | PROT_WRITE, 3456 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, 3457 | -1, 0); 3458 | if (sshfs.password == MAP_FAILED) { 3459 | perror("Failed to allocate locked page for password"); 3460 | return -1; 3461 | } 3462 | #ifdef __APPLE__ 3463 | if (mlock(sshfs.password, size) != 0) { 3464 | memset(sshfs.password, 0, size); 3465 | munmap(sshfs.password, size); 3466 | sshfs.password = NULL; 3467 | perror("Failed to allocate locked page for password"); 3468 | return -1; 3469 | } 3470 | #endif /* __APPLE__ */ 3471 | 3472 | /* Don't use fgets() because password might stay in memory */ 3473 | for (n = 0; n < max_password; n++) { 3474 | int res; 3475 | 3476 | res = read(0, &sshfs.password[n], 1); 3477 | if (res == -1) { 3478 | perror("Reading password"); 3479 | return -1; 3480 | } 3481 | if (res == 0) { 3482 | sshfs.password[n] = '\n'; 3483 | break; 3484 | } 3485 | if (sshfs.password[n] == '\n') 3486 | break; 3487 | } 3488 | if (n == max_password) { 3489 | fprintf(stderr, "Password too long\n"); 3490 | return -1; 3491 | } 3492 | sshfs.password[n+1] = '\0'; 3493 | ssh_add_arg("-oNumberOfPasswordPrompts=1"); 3494 | 3495 | return 0; 3496 | } 3497 | 3498 | static void set_ssh_command(void) 3499 | { 3500 | char *s; 3501 | char *d; 3502 | int i = 0; 3503 | int end = 0; 3504 | 3505 | d = sshfs.ssh_command; 3506 | s = sshfs.ssh_command; 3507 | while (!end) { 3508 | switch (*s) { 3509 | case '\0': 3510 | end = 1; 3511 | case ' ': 3512 | *d = '\0'; 3513 | if (i == 0) { 3514 | replace_arg(&sshfs.ssh_args.argv[0], 3515 | sshfs.ssh_command); 3516 | } else { 3517 | if (fuse_opt_insert_arg(&sshfs.ssh_args, i, 3518 | sshfs.ssh_command) == -1) 3519 | _exit(1); 3520 | } 3521 | i++; 3522 | d = sshfs.ssh_command; 3523 | break; 3524 | 3525 | case '\\': 3526 | if (s[1]) 3527 | s++; 3528 | default: 3529 | *d++ = *s; 3530 | } 3531 | s++; 3532 | } 3533 | } 3534 | 3535 | static char *find_base_path(void) 3536 | { 3537 | char *s = sshfs.host; 3538 | char *d = s; 3539 | 3540 | for (; *s && *s != ':'; s++) { 3541 | if (*s == '[') { 3542 | /* 3543 | * Handle IPv6 numerical address enclosed in square 3544 | * brackets 3545 | */ 3546 | s++; 3547 | for (; *s != ']'; s++) { 3548 | if (!*s) { 3549 | fprintf(stderr, "missing ']' in hostname\n"); 3550 | exit(1); 3551 | } 3552 | *d++ = *s; 3553 | } 3554 | } else { 3555 | *d++ = *s; 3556 | } 3557 | 3558 | } 3559 | *d++ = '\0'; 3560 | s++; 3561 | 3562 | return s; 3563 | } 3564 | 3565 | /* 3566 | * Remove commas from fsname, as it confuses the fuse option parser. 3567 | */ 3568 | static void fsname_remove_commas(char *fsname) 3569 | { 3570 | if (strchr(fsname, ',') != NULL) { 3571 | char *s = fsname; 3572 | char *d = s; 3573 | 3574 | for (; *s; s++) { 3575 | if (*s != ',') 3576 | *d++ = *s; 3577 | } 3578 | *d = *s; 3579 | } 3580 | } 3581 | 3582 | #if FUSE_VERSION >= 27 3583 | static char *fsname_escape_commas(char *fsnameold) 3584 | { 3585 | char *fsname = g_malloc(strlen(fsnameold) * 2 + 1); 3586 | char *d = fsname; 3587 | char *s; 3588 | 3589 | for (s = fsnameold; *s; s++) { 3590 | if (*s == '\\' || *s == ',') 3591 | *d++ = '\\'; 3592 | *d++ = *s; 3593 | } 3594 | *d = '\0'; 3595 | g_free(fsnameold); 3596 | 3597 | return fsname; 3598 | } 3599 | #endif 3600 | 3601 | static int ssh_connect(void) 3602 | { 3603 | int res; 3604 | 3605 | res = processing_init(); 3606 | if (res == -1) 3607 | return -1; 3608 | 3609 | if (!sshfs.delay_connect) { 3610 | if (connect_remote() == -1) 3611 | return -1; 3612 | 3613 | if (!sshfs.no_check_root && 3614 | sftp_check_root(sshfs.base_path) != 0) 3615 | return -1; 3616 | 3617 | } 3618 | return 0; 3619 | } 3620 | 3621 | /* number of ':' separated fields in a passwd/group file that we care 3622 | * about */ 3623 | #define IDMAP_FIELDS 3 3624 | 3625 | /* given a line from a uidmap or gidmap, parse out the name and id */ 3626 | static void parse_idmap_line(char *line, const char* filename, 3627 | const unsigned int lineno, uint32_t *ret_id, char **ret_name, 3628 | const int eof) 3629 | { 3630 | /* chomp off the trailing newline */ 3631 | char *p = line; 3632 | if ((p = strrchr(line, '\n'))) 3633 | *p = '\0'; 3634 | else if (!eof) { 3635 | fprintf(stderr, "%s:%u: line too long\n", filename, lineno); 3636 | exit(1); 3637 | } 3638 | char *tokens[IDMAP_FIELDS]; 3639 | char *tok; 3640 | int i; 3641 | for (i = 0; (tok = strsep(&line, ":")) && (i < IDMAP_FIELDS) ; i++) { 3642 | tokens[i] = tok; 3643 | } 3644 | 3645 | char *name_tok, *id_tok; 3646 | if (i == 2) { 3647 | /* assume name:id format */ 3648 | name_tok = tokens[0]; 3649 | id_tok = tokens[1]; 3650 | } else if (i >= IDMAP_FIELDS) { 3651 | /* assume passwd/group file format */ 3652 | name_tok = tokens[0]; 3653 | id_tok = tokens[2]; 3654 | } else { 3655 | fprintf(stderr, "%s:%u: unknown format\n", filename, lineno); 3656 | exit(1); 3657 | } 3658 | 3659 | errno = 0; 3660 | uint32_t remote_id = strtoul(id_tok, NULL, 10); 3661 | if (errno) { 3662 | fprintf(stderr, "Invalid id number on line %u of '%s': %s\n", 3663 | lineno, filename, strerror(errno)); 3664 | exit(1); 3665 | } 3666 | 3667 | *ret_name = strdup(name_tok); 3668 | *ret_id = remote_id; 3669 | } 3670 | 3671 | /* read a uidmap or gidmap */ 3672 | static void read_id_map(char *file, uint32_t *(*map_fn)(char *), 3673 | const char *name_id, GHashTable **idmap, GHashTable **r_idmap) 3674 | { 3675 | *idmap = g_hash_table_new(NULL, NULL); 3676 | *r_idmap = g_hash_table_new(NULL, NULL); 3677 | FILE *fp; 3678 | char line[LINE_MAX]; 3679 | unsigned int lineno = 0; 3680 | uid_t local_uid = getuid(); 3681 | 3682 | fp = fopen(file, "r"); 3683 | if (fp == NULL) { 3684 | fprintf(stderr, "failed to open '%s': %s\n", 3685 | file, strerror(errno)); 3686 | exit(1); 3687 | } 3688 | struct stat st; 3689 | if (fstat(fileno(fp), &st) == -1) { 3690 | fprintf(stderr, "failed to stat '%s': %s\n", file, 3691 | strerror(errno)); 3692 | exit(1); 3693 | } 3694 | if (st.st_uid != local_uid) { 3695 | fprintf(stderr, "'%s' is not owned by uid %lu\n", file, 3696 | (unsigned long)local_uid); 3697 | exit(1); 3698 | } 3699 | if (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) { 3700 | fprintf(stderr, "'%s' is writable by other users\n", file); 3701 | exit(1); 3702 | } 3703 | 3704 | while (fgets(line, LINE_MAX, fp) != NULL) { 3705 | lineno++; 3706 | uint32_t remote_id; 3707 | char *name; 3708 | 3709 | /* skip blank lines */ 3710 | if (line[0] == '\n' || line[0] == '\0') 3711 | continue; 3712 | 3713 | parse_idmap_line(line, file, lineno, &remote_id, &name, feof(fp)); 3714 | 3715 | uint32_t *local_id = map_fn(name); 3716 | if (local_id == NULL) { 3717 | /* not found */ 3718 | DEBUG("%s(%u): no local %s\n", name, remote_id, name_id); 3719 | free(name); 3720 | continue; 3721 | } 3722 | 3723 | DEBUG("%s: remote %s %u => local %s %u\n", 3724 | name, name_id, remote_id, name_id, *local_id); 3725 | g_hash_table_insert(*idmap, GUINT_TO_POINTER(remote_id), GUINT_TO_POINTER(*local_id)); 3726 | g_hash_table_insert(*r_idmap, GUINT_TO_POINTER(*local_id), GUINT_TO_POINTER(remote_id)); 3727 | free(name); 3728 | free(local_id); 3729 | } 3730 | 3731 | if (fclose(fp) == EOF) { 3732 | fprintf(stderr, "failed to close '%s': %s", 3733 | file, strerror(errno)); 3734 | exit(1); 3735 | } 3736 | } 3737 | 3738 | /* given a username, return a pointer to its uid, or NULL if it doesn't 3739 | * exist on this system */ 3740 | static uint32_t *username_to_uid(char *name) 3741 | { 3742 | errno = 0; 3743 | struct passwd *pw = getpwnam(name); 3744 | if (pw == NULL) { 3745 | if (errno == 0) { 3746 | /* "does not exist" */ 3747 | return NULL; 3748 | } 3749 | fprintf(stderr, "Failed to look up user '%s': %s\n", 3750 | name, strerror(errno)); 3751 | exit(1); 3752 | } 3753 | uint32_t *r = malloc(sizeof(uint32_t)); 3754 | if (r == NULL) { 3755 | fprintf(stderr, "sshfs: memory allocation failed\n"); 3756 | abort(); 3757 | } 3758 | *r = pw->pw_uid; 3759 | return r; 3760 | } 3761 | 3762 | /* given a groupname, return a pointer to its gid, or NULL if it doesn't 3763 | * exist on this system */ 3764 | static uint32_t *groupname_to_gid(char *name) 3765 | { 3766 | errno = 0; 3767 | struct group *gr = getgrnam(name); 3768 | if (gr == NULL) { 3769 | if (errno == 0) { 3770 | /* "does not exist" */ 3771 | return NULL; 3772 | } 3773 | fprintf(stderr, "Failed to look up group '%s': %s\n", 3774 | name, strerror(errno)); 3775 | exit(1); 3776 | } 3777 | uint32_t *r = malloc(sizeof(uint32_t)); 3778 | if (r == NULL) { 3779 | fprintf(stderr, "sshfs: memory allocation failed\n"); 3780 | abort(); 3781 | } 3782 | *r = gr->gr_gid; 3783 | return r; 3784 | } 3785 | 3786 | static inline void load_uid_map(void) 3787 | { 3788 | read_id_map(sshfs.uid_file, &username_to_uid, "uid", &sshfs.uid_map, &sshfs.r_uid_map); 3789 | } 3790 | 3791 | static inline void load_gid_map(void) 3792 | { 3793 | read_id_map(sshfs.gid_file, &groupname_to_gid, "gid", &sshfs.gid_map, &sshfs.r_gid_map); 3794 | } 3795 | 3796 | int main(int argc, char *argv[], __unused char *envp[], char **exec_path) 3797 | { 3798 | #ifdef __APPLE__ 3799 | if (!realpath(*exec_path, sshfs_program_path)) { 3800 | memset(sshfs_program_path, 0, PATH_MAX); 3801 | } 3802 | #endif 3803 | int res; 3804 | struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 3805 | char *tmp; 3806 | char *fsname; 3807 | const char *sftp_server; 3808 | int libver; 3809 | 3810 | #ifdef __APPLE__ 3811 | /* Until this gets fixed somewhere else. */ 3812 | g_slice_set_config(G_SLICE_CONFIG_ALWAYS_MALLOC, TRUE); 3813 | #endif /* __APPLE__ */ 3814 | g_thread_init(NULL); 3815 | 3816 | sshfs.blksize = 4096; 3817 | /* SFTP spec says all servers should allow at least 32k I/O */ 3818 | sshfs.max_read = 32768; 3819 | sshfs.max_write = 32768; 3820 | sshfs.nodelay_workaround = 1; 3821 | sshfs.nodelaysrv_workaround = 0; 3822 | #ifdef __APPLE__ 3823 | sshfs.rename_workaround = 1; 3824 | #else 3825 | sshfs.rename_workaround = 0; 3826 | #endif /* __APPLE__ */ 3827 | sshfs.truncate_workaround = 0; 3828 | sshfs.buflimit_workaround = 1; 3829 | sshfs.ssh_ver = 2; 3830 | sshfs.progname = argv[0]; 3831 | sshfs.rfd = -1; 3832 | sshfs.wfd = -1; 3833 | sshfs.ptyfd = -1; 3834 | sshfs.ptyslavefd = -1; 3835 | sshfs.delay_connect = 0; 3836 | sshfs.slave = 0; 3837 | sshfs.detect_uid = 0; 3838 | sshfs.idmap = IDMAP_NONE; 3839 | sshfs.nomap = NOMAP_ERROR; 3840 | ssh_add_arg("ssh"); 3841 | ssh_add_arg("-x"); 3842 | ssh_add_arg("-a"); 3843 | ssh_add_arg("-oClearAllForwardings=yes"); 3844 | 3845 | #ifdef __APPLE__ 3846 | sshfs.detect_uid = 1; 3847 | #endif 3848 | 3849 | if (fuse_opt_parse(&args, &sshfs, sshfs_opts, sshfs_opt_proc) == -1 || 3850 | parse_workarounds() == -1) 3851 | exit(1); 3852 | 3853 | if (sshfs.idmap == IDMAP_USER) 3854 | sshfs.detect_uid = 1; 3855 | else if (sshfs.idmap == IDMAP_FILE) { 3856 | sshfs.uid_map = NULL; 3857 | sshfs.gid_map = NULL; 3858 | sshfs.r_uid_map = NULL; 3859 | sshfs.r_gid_map = NULL; 3860 | if (!sshfs.uid_file && !sshfs.gid_file) { 3861 | fprintf(stderr, "need a uidfile or gidfile with idmap=file\n"); 3862 | exit(1); 3863 | } 3864 | if (sshfs.uid_file) 3865 | load_uid_map(); 3866 | if (sshfs.gid_file) 3867 | load_gid_map(); 3868 | } 3869 | free(sshfs.uid_file); 3870 | free(sshfs.gid_file); 3871 | 3872 | DEBUG("SSHFS version %s\n", PACKAGE_VERSION); 3873 | 3874 | if (sshfs.slave) { 3875 | /* Force sshfs to the foreground when using stdin+stdout */ 3876 | sshfs.foreground = 1; 3877 | } 3878 | 3879 | if (sshfs.slave && sshfs.password_stdin) { 3880 | fprintf(stderr, "the password_stdin and slave options cannot both be specified\n"); 3881 | exit(1); 3882 | } 3883 | 3884 | if (sshfs.password_stdin) { 3885 | res = read_password(); 3886 | if (res == -1) 3887 | exit(1); 3888 | } 3889 | 3890 | if (sshfs.buflimit_workaround) 3891 | /* Work around buggy sftp-server in OpenSSH. Without this on 3892 | a slow server a 10Mbyte buffer would fill up and the server 3893 | would abort */ 3894 | sshfs.max_outstanding_len = 8388608; 3895 | else 3896 | sshfs.max_outstanding_len = ~0; 3897 | 3898 | if (!sshfs.host) { 3899 | fprintf(stderr, "missing host\n"); 3900 | fprintf(stderr, "see `%s -h' for usage\n", argv[0]); 3901 | exit(1); 3902 | } 3903 | 3904 | fsname = g_strdup(sshfs.host); 3905 | sshfs.base_path = g_strdup(find_base_path()); 3906 | 3907 | if (sshfs.ssh_command) 3908 | set_ssh_command(); 3909 | 3910 | tmp = g_strdup_printf("-%i", sshfs.ssh_ver); 3911 | ssh_add_arg(tmp); 3912 | g_free(tmp); 3913 | ssh_add_arg(sshfs.host); 3914 | if (sshfs.sftp_server) 3915 | sftp_server = sshfs.sftp_server; 3916 | else if (sshfs.ssh_ver == 1) 3917 | sftp_server = SFTP_SERVER_PATH; 3918 | else 3919 | sftp_server = "sftp"; 3920 | 3921 | if (sshfs.ssh_ver != 1 && strchr(sftp_server, '/') == NULL) 3922 | ssh_add_arg("-s"); 3923 | 3924 | ssh_add_arg(sftp_server); 3925 | free(sshfs.sftp_server); 3926 | 3927 | 3928 | res = cache_parse_options(&args); 3929 | if (res == -1) 3930 | exit(1); 3931 | 3932 | sshfs.randseed = time(0); 3933 | 3934 | if (sshfs.max_read > 65536) 3935 | sshfs.max_read = 65536; 3936 | if (sshfs.max_write > 65536) 3937 | sshfs.max_write = 65536; 3938 | 3939 | if (fuse_is_lib_option("ac_attr_timeout=")) 3940 | fuse_opt_insert_arg(&args, 1, "-oauto_cache,ac_attr_timeout=0"); 3941 | #if FUSE_VERSION >= 27 3942 | libver = fuse_version(); 3943 | assert(libver >= 27); 3944 | if (libver >= 28) 3945 | fsname = fsname_escape_commas(fsname); 3946 | else 3947 | fsname_remove_commas(fsname); 3948 | tmp = g_strdup_printf("-osubtype=sshfs,fsname=%s", fsname); 3949 | #else 3950 | fsname_remove_commas(fsname); 3951 | tmp = g_strdup_printf("-ofsname=sshfs#%s", fsname); 3952 | #endif 3953 | fuse_opt_insert_arg(&args, 1, tmp); 3954 | g_free(tmp); 3955 | g_free(fsname); 3956 | check_large_read(&args); 3957 | 3958 | #if FUSE_VERSION >= 26 3959 | { 3960 | struct fuse *fuse; 3961 | struct fuse_chan *ch; 3962 | char *mountpoint; 3963 | int multithreaded; 3964 | int foreground; 3965 | struct stat st; 3966 | 3967 | res = fuse_parse_cmdline(&args, &mountpoint, &multithreaded, 3968 | &foreground); 3969 | if (res == -1) 3970 | exit(1); 3971 | 3972 | if (sshfs.slave) { 3973 | /* Force sshfs to the foreground when using stdin+stdout */ 3974 | foreground = 1; 3975 | } 3976 | 3977 | res = stat(mountpoint, &st); 3978 | if (res == -1) { 3979 | perror(mountpoint); 3980 | exit(1); 3981 | } 3982 | sshfs.mnt_mode = st.st_mode; 3983 | 3984 | ch = fuse_mount(mountpoint, &args); 3985 | if (!ch) 3986 | exit(1); 3987 | 3988 | res = fcntl(fuse_chan_fd(ch), F_SETFD, FD_CLOEXEC); 3989 | if (res == -1) 3990 | perror("WARNING: failed to set FD_CLOEXEC on fuse device"); 3991 | 3992 | fuse = fuse_new(ch, &args, cache_init(&sshfs_oper), 3993 | sizeof(struct fuse_operations), NULL); 3994 | if (fuse == NULL) { 3995 | fuse_unmount(mountpoint, ch); 3996 | exit(1); 3997 | } 3998 | 3999 | res = ssh_connect(); 4000 | if (res == -1) { 4001 | fuse_unmount(mountpoint, ch); 4002 | fuse_destroy(fuse); 4003 | exit(1); 4004 | } 4005 | 4006 | res = fuse_daemonize(foreground); 4007 | if (res != -1) 4008 | res = fuse_set_signal_handlers(fuse_get_session(fuse)); 4009 | 4010 | if (res == -1) { 4011 | fuse_unmount(mountpoint, ch); 4012 | fuse_destroy(fuse); 4013 | exit(1); 4014 | } 4015 | 4016 | if (multithreaded) 4017 | res = fuse_loop_mt(fuse); 4018 | else 4019 | res = fuse_loop(fuse); 4020 | 4021 | if (res == -1) 4022 | res = 1; 4023 | else 4024 | res = 0; 4025 | 4026 | fuse_remove_signal_handlers(fuse_get_session(fuse)); 4027 | fuse_unmount(mountpoint, ch); 4028 | fuse_destroy(fuse); 4029 | free(mountpoint); 4030 | } 4031 | #else 4032 | res = ssh_connect(); 4033 | if (res == -1) 4034 | exit(1); 4035 | 4036 | res = sshfs_fuse_main(&args); 4037 | #endif 4038 | 4039 | if (sshfs.debug) { 4040 | unsigned int avg_rtt = 0; 4041 | 4042 | if (sshfs.num_sent) 4043 | avg_rtt = sshfs.total_rtt / sshfs.num_sent; 4044 | 4045 | DEBUG("\n" 4046 | "sent: %llu messages, %llu bytes\n" 4047 | "received: %llu messages, %llu bytes\n" 4048 | "rtt min/max/avg: %ums/%ums/%ums\n" 4049 | "num connect: %u\n", 4050 | (unsigned long long) sshfs.num_sent, 4051 | (unsigned long long) sshfs.bytes_sent, 4052 | (unsigned long long) sshfs.num_received, 4053 | (unsigned long long) sshfs.bytes_received, 4054 | sshfs.min_rtt, sshfs.max_rtt, avg_rtt, 4055 | sshfs.num_connect); 4056 | } 4057 | 4058 | fuse_opt_free_args(&args); 4059 | fuse_opt_free_args(&sshfs.ssh_args); 4060 | free(sshfs.directport); 4061 | 4062 | return res; 4063 | } 4064 | -------------------------------------------------------------------------------- /sshfs.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DE0F21EB139A7AFF003CBAC9 /* sshfs.c in Sources */ = {isa = PBXBuildFile; fileRef = DE0F21EA139A7AFF003CBAC9 /* sshfs.c */; }; 11 | DE0F21EF139A7B0A003CBAC9 /* cache.c in Sources */ = {isa = PBXBuildFile; fileRef = DE0F21ED139A7B0A003CBAC9 /* cache.c */; }; 12 | DE0F21F1139A7B11003CBAC9 /* sshnodelay.c in Sources */ = {isa = PBXBuildFile; fileRef = DE0F21F0139A7B11003CBAC9 /* sshnodelay.c */; }; 13 | DE37D32813CF7B660027D6F9 /* darwin_semaphore.c in Sources */ = {isa = PBXBuildFile; fileRef = DE37D32413CF7B660027D6F9 /* darwin_semaphore.c */; }; 14 | DE37D32913CF7B660027D6F9 /* fuse_opt.c in Sources */ = {isa = PBXBuildFile; fileRef = DE37D32613CF7B660027D6F9 /* fuse_opt.c */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXCopyFilesBuildPhase section */ 18 | DE0F21D3139A7ABB003CBAC9 /* CopyFiles */ = { 19 | isa = PBXCopyFilesBuildPhase; 20 | buildActionMask = 2147483647; 21 | dstPath = /usr/share/man/man1/; 22 | dstSubfolderSpec = 0; 23 | files = ( 24 | ); 25 | runOnlyForDeploymentPostprocessing = 1; 26 | }; 27 | /* End PBXCopyFilesBuildPhase section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | DE0F21D5139A7ABB003CBAC9 /* sshfs */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = sshfs; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | DE0F21E9139A7AFF003CBAC9 /* sshfs.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; path = sshfs.1; sourceTree = ""; }; 32 | DE0F21EA139A7AFF003CBAC9 /* sshfs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sshfs.c; sourceTree = ""; }; 33 | DE0F21ED139A7B0A003CBAC9 /* cache.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cache.c; sourceTree = ""; }; 34 | DE0F21EE139A7B0A003CBAC9 /* cache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cache.h; sourceTree = ""; }; 35 | DE0F21F0139A7B11003CBAC9 /* sshnodelay.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sshnodelay.c; sourceTree = ""; }; 36 | DE0F21F2139A7B1D003CBAC9 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = ""; }; 37 | DE0F21FC139B1EC3003CBAC9 /* configure.ac */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = configure.ac; sourceTree = ""; }; 38 | DE0F21FE139B1ECB003CBAC9 /* Makefile.am */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Makefile.am; sourceTree = ""; }; 39 | DE37D32413CF7B660027D6F9 /* darwin_semaphore.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = darwin_semaphore.c; sourceTree = ""; }; 40 | DE37D32513CF7B660027D6F9 /* darwin_semaphore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = darwin_semaphore.h; sourceTree = ""; }; 41 | DE37D32613CF7B660027D6F9 /* fuse_opt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fuse_opt.c; sourceTree = ""; }; 42 | DE37D32713CF7B660027D6F9 /* fuse_opt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fuse_opt.h; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | DE0F21D2139A7ABB003CBAC9 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | DE0F21CA139A7ABB003CBAC9 = { 57 | isa = PBXGroup; 58 | children = ( 59 | DE37D32313CF7B660027D6F9 /* compat */, 60 | DE0F21FE139B1ECB003CBAC9 /* Makefile.am */, 61 | DE0F21FC139B1EC3003CBAC9 /* configure.ac */, 62 | DE0F21F2139A7B1D003CBAC9 /* config.h */, 63 | DE0F21F0139A7B11003CBAC9 /* sshnodelay.c */, 64 | DE0F21ED139A7B0A003CBAC9 /* cache.c */, 65 | DE0F21EE139A7B0A003CBAC9 /* cache.h */, 66 | DE0F21E9139A7AFF003CBAC9 /* sshfs.1 */, 67 | DE0F21EA139A7AFF003CBAC9 /* sshfs.c */, 68 | DE0F21D6139A7ABB003CBAC9 /* Products */, 69 | ); 70 | sourceTree = ""; 71 | }; 72 | DE0F21D6139A7ABB003CBAC9 /* Products */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | DE0F21D5139A7ABB003CBAC9 /* sshfs */, 76 | ); 77 | name = Products; 78 | sourceTree = ""; 79 | }; 80 | DE37D32313CF7B660027D6F9 /* compat */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | DE37D32413CF7B660027D6F9 /* darwin_semaphore.c */, 84 | DE37D32513CF7B660027D6F9 /* darwin_semaphore.h */, 85 | DE37D32613CF7B660027D6F9 /* fuse_opt.c */, 86 | DE37D32713CF7B660027D6F9 /* fuse_opt.h */, 87 | ); 88 | path = compat; 89 | sourceTree = ""; 90 | }; 91 | /* End PBXGroup section */ 92 | 93 | /* Begin PBXNativeTarget section */ 94 | DE0F21D4139A7ABB003CBAC9 /* sshfs */ = { 95 | isa = PBXNativeTarget; 96 | buildConfigurationList = DE0F21DE139A7ABB003CBAC9 /* Build configuration list for PBXNativeTarget "sshfs" */; 97 | buildPhases = ( 98 | DE0F21D1139A7ABB003CBAC9 /* Sources */, 99 | DE0F21D2139A7ABB003CBAC9 /* Frameworks */, 100 | DE0F21D3139A7ABB003CBAC9 /* CopyFiles */, 101 | ); 102 | buildRules = ( 103 | ); 104 | dependencies = ( 105 | ); 106 | name = sshfs; 107 | productName = sshfs; 108 | productReference = DE0F21D5139A7ABB003CBAC9 /* sshfs */; 109 | productType = "com.apple.product-type.tool"; 110 | }; 111 | /* End PBXNativeTarget section */ 112 | 113 | /* Begin PBXProject section */ 114 | DE0F21CC139A7ABB003CBAC9 /* Project object */ = { 115 | isa = PBXProject; 116 | buildConfigurationList = DE0F21CF139A7ABB003CBAC9 /* Build configuration list for PBXProject "sshfs" */; 117 | compatibilityVersion = "Xcode 3.2"; 118 | developmentRegion = English; 119 | hasScannedForEncodings = 0; 120 | knownRegions = ( 121 | en, 122 | ); 123 | mainGroup = DE0F21CA139A7ABB003CBAC9; 124 | productRefGroup = DE0F21D6139A7ABB003CBAC9 /* Products */; 125 | projectDirPath = ""; 126 | projectRoot = ""; 127 | targets = ( 128 | DE0F21D4139A7ABB003CBAC9 /* sshfs */, 129 | ); 130 | }; 131 | /* End PBXProject section */ 132 | 133 | /* Begin PBXSourcesBuildPhase section */ 134 | DE0F21D1139A7ABB003CBAC9 /* Sources */ = { 135 | isa = PBXSourcesBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | DE0F21EB139A7AFF003CBAC9 /* sshfs.c in Sources */, 139 | DE0F21EF139A7B0A003CBAC9 /* cache.c in Sources */, 140 | DE0F21F1139A7B11003CBAC9 /* sshnodelay.c in Sources */, 141 | DE37D32813CF7B660027D6F9 /* darwin_semaphore.c in Sources */, 142 | DE37D32913CF7B660027D6F9 /* fuse_opt.c in Sources */, 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | /* End PBXSourcesBuildPhase section */ 147 | 148 | /* Begin XCBuildConfiguration section */ 149 | DE0F21DC139A7ABB003CBAC9 /* Debug */ = { 150 | isa = XCBuildConfiguration; 151 | buildSettings = { 152 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 153 | GCC_C_LANGUAGE_STANDARD = gnu99; 154 | GCC_OPTIMIZATION_LEVEL = 0; 155 | GCC_PREPROCESSOR_DEFINITIONS = DEBUG; 156 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 157 | GCC_VERSION = ""; 158 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 159 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 160 | GCC_WARN_UNUSED_VARIABLE = YES; 161 | MACOSX_DEPLOYMENT_TARGET = ""; 162 | ONLY_ACTIVE_ARCH = YES; 163 | SDKROOT = ""; 164 | }; 165 | name = Debug; 166 | }; 167 | DE0F21DD139A7ABB003CBAC9 /* Release */ = { 168 | isa = XCBuildConfiguration; 169 | buildSettings = { 170 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 171 | GCC_C_LANGUAGE_STANDARD = gnu99; 172 | GCC_VERSION = ""; 173 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 174 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 175 | GCC_WARN_UNUSED_VARIABLE = YES; 176 | MACOSX_DEPLOYMENT_TARGET = ""; 177 | SDKROOT = ""; 178 | }; 179 | name = Release; 180 | }; 181 | DE0F21DF139A7ABB003CBAC9 /* Debug */ = { 182 | isa = XCBuildConfiguration; 183 | buildSettings = { 184 | ALWAYS_SEARCH_USER_PATHS = NO; 185 | COPY_PHASE_STRIP = NO; 186 | GCC_DYNAMIC_NO_PIC = NO; 187 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 188 | MACOSX_DEPLOYMENT_TARGET = ""; 189 | PRODUCT_NAME = "$(TARGET_NAME)"; 190 | }; 191 | name = Debug; 192 | }; 193 | DE0F21E0139A7ABB003CBAC9 /* Release */ = { 194 | isa = XCBuildConfiguration; 195 | buildSettings = { 196 | ALWAYS_SEARCH_USER_PATHS = NO; 197 | COPY_PHASE_STRIP = YES; 198 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 199 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 200 | MACOSX_DEPLOYMENT_TARGET = ""; 201 | PRODUCT_NAME = "$(TARGET_NAME)"; 202 | }; 203 | name = Release; 204 | }; 205 | /* End XCBuildConfiguration section */ 206 | 207 | /* Begin XCConfigurationList section */ 208 | DE0F21CF139A7ABB003CBAC9 /* Build configuration list for PBXProject "sshfs" */ = { 209 | isa = XCConfigurationList; 210 | buildConfigurations = ( 211 | DE0F21DC139A7ABB003CBAC9 /* Debug */, 212 | DE0F21DD139A7ABB003CBAC9 /* Release */, 213 | ); 214 | defaultConfigurationIsVisible = 0; 215 | defaultConfigurationName = Release; 216 | }; 217 | DE0F21DE139A7ABB003CBAC9 /* Build configuration list for PBXNativeTarget "sshfs" */ = { 218 | isa = XCConfigurationList; 219 | buildConfigurations = ( 220 | DE0F21DF139A7ABB003CBAC9 /* Debug */, 221 | DE0F21E0139A7ABB003CBAC9 /* Release */, 222 | ); 223 | defaultConfigurationIsVisible = 0; 224 | defaultConfigurationName = Release; 225 | }; 226 | /* End XCConfigurationList section */ 227 | }; 228 | rootObject = DE0F21CC139A7ABB003CBAC9 /* Project object */; 229 | } 230 | -------------------------------------------------------------------------------- /sshfs.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sshnodelay.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __APPLE__ 9 | 10 | int custom_connect(int sock, const struct sockaddr *addr, socklen_t addrlen); 11 | 12 | typedef struct interpose_s { 13 | void *new_func; 14 | void *orig_func; 15 | } interpose_t; 16 | 17 | static const interpose_t interposers[] \ 18 | __attribute__ ((section("__DATA, __interpose"))) = { 19 | { (void *)custom_connect, (void *)connect }, 20 | }; 21 | 22 | int custom_connect(int sock, const struct sockaddr *addr, socklen_t addrlen) 23 | { 24 | int res = connect(sock, addr, addrlen); 25 | if (!res && addr->sa_family == AF_INET) { 26 | int opt = 1; 27 | setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); 28 | } 29 | return res; 30 | } 31 | 32 | #else 33 | 34 | int connect(int sock, const struct sockaddr *addr, socklen_t addrlen) 35 | { 36 | int (*next_connect)(int, const struct sockaddr *, socklen_t) = 37 | dlsym(RTLD_NEXT, "connect"); 38 | int res = next_connect(sock, addr, addrlen); 39 | if (!res && addr->sa_family == AF_INET) { 40 | int opt = 1; 41 | setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); 42 | } 43 | return res; 44 | } 45 | 46 | #endif 47 | --------------------------------------------------------------------------------