1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs2.provider.sftp;
18
19 import java.io.IOException;
20 import java.io.InputStreamReader;
21 import java.nio.charset.StandardCharsets;
22 import java.time.Duration;
23 import java.util.Collection;
24 import java.util.Objects;
25
26 import org.apache.commons.lang3.time.DurationUtils;
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.commons.vfs2.Capability;
30 import org.apache.commons.vfs2.FileObject;
31 import org.apache.commons.vfs2.FileSystemException;
32 import org.apache.commons.vfs2.FileSystemOptions;
33 import org.apache.commons.vfs2.provider.AbstractFileName;
34 import org.apache.commons.vfs2.provider.AbstractFileSystem;
35 import org.apache.commons.vfs2.provider.GenericFileName;
36
37 import com.jcraft.jsch.ChannelExec;
38 import com.jcraft.jsch.ChannelSftp;
39 import com.jcraft.jsch.JSchException;
40 import com.jcraft.jsch.Session;
41 import com.jcraft.jsch.SftpException;
42
43
44
45
46 public class SftpFileSystem extends AbstractFileSystem {
47
48 private static final Log LOG = LogFactory.getLog(SftpFileSystem.class);
49
50 private static final int UNIDENTIFED = -1;
51
52 private static final int SLEEP_MILLIS = 100;
53
54 private static final int EXEC_BUFFER_SIZE = 128;
55
56 private static final long LAST_MOD_TIME_ACCURACY = 1000L;
57
58
59
60
61
62
63
64 private volatile Session session;
65
66 private volatile ChannelSftp idleChannel;
67
68 private final Duration connectTimeout;
69
70
71
72
73
74
75
76 private volatile int uid = UNIDENTIFED;
77
78
79
80
81
82
83
84 private volatile int[] groupsIds;
85
86
87
88
89 private final boolean execDisabled;
90
91 protected SftpFileSystem(final GenericFileName rootName, final Session session,
92 final FileSystemOptions fileSystemOptions) {
93 super(rootName, null, fileSystemOptions);
94 this.session = Objects.requireNonNull(session, "session");
95 this.connectTimeout = SftpFileSystemConfigBuilder.getInstance().getConnectTimeout(fileSystemOptions);
96
97 if (SftpFileSystemConfigBuilder.getInstance().isDisableDetectExecChannel(fileSystemOptions)) {
98 this.execDisabled = true;
99 } else {
100 this.execDisabled = detectExecDisabled();
101 }
102 }
103
104
105
106
107 @Override
108 protected void addCapabilities(final Collection<Capability> caps) {
109 caps.addAll(SftpFileProvider.capabilities);
110 }
111
112
113
114
115 @Override
116 protected FileObject createFile(final AbstractFileName name) throws FileSystemException {
117 return new SftpFileObject(name, this);
118 }
119
120
121
122
123
124
125 private boolean detectExecDisabled() {
126 try {
127 return getUId() == UNIDENTIFED;
128 } catch(final JSchException | IOException e) {
129 LOG.debug("Cannot get UID, assuming no exec channel is present", e);
130 return true;
131 }
132 }
133
134 @Override
135 protected void doCloseCommunicationLink() {
136 if (idleChannel != null) {
137 synchronized (this) {
138 if (idleChannel != null) {
139 idleChannel.disconnect();
140 idleChannel = null;
141 }
142 }
143 }
144
145 if (session != null) {
146 session.disconnect();
147 }
148 }
149
150
151
152
153
154
155
156
157
158
159
160 private int executeCommand(final String command, final StringBuilder output) throws JSchException, IOException {
161 final ChannelExec channel = (ChannelExec) getSession().openChannel("exec");
162 try {
163 channel.setCommand(command);
164 channel.setInputStream(null);
165 try (final InputStreamReader stream = new InputStreamReader(channel.getInputStream(), StandardCharsets.UTF_8)) {
166 channel.setErrStream(System.err, true);
167 channel.connect(DurationUtils.toMillisInt(connectTimeout));
168
169
170 final char[] buffer = new char[EXEC_BUFFER_SIZE];
171 int read;
172 while ((read = stream.read(buffer, 0, buffer.length)) >= 0) {
173 output.append(buffer, 0, read);
174 }
175 }
176
177
178 while (!channel.isClosed()) {
179 try {
180 Thread.sleep(SLEEP_MILLIS);
181 } catch (final Exception ee) {
182
183 }
184 }
185 } finally {
186 channel.disconnect();
187 }
188 return channel.getExitStatus();
189 }
190
191
192
193
194
195
196
197
198 protected ChannelSftp getChannel() throws IOException {
199 try {
200
201 ChannelSftp channel = null;
202 if (idleChannel != null) {
203 synchronized (this) {
204 if (idleChannel != null) {
205 channel = idleChannel;
206 idleChannel = null;
207 }
208 }
209 }
210
211 if (channel == null) {
212 channel = (ChannelSftp) getSession().openChannel("sftp");
213 channel.connect(DurationUtils.toMillisInt(connectTimeout));
214 final Boolean userDirIsRoot = SftpFileSystemConfigBuilder.getInstance()
215 .getUserDirIsRoot(getFileSystemOptions());
216 final String workingDirectory = getRootName().getPath();
217 if (workingDirectory != null && (userDirIsRoot == null || !userDirIsRoot.booleanValue())) {
218 try {
219 channel.cd(workingDirectory);
220 } catch (final SftpException e) {
221 throw new FileSystemException("vfs.provider.sftp/change-work-directory.error", workingDirectory,
222 e);
223 }
224 }
225 }
226
227 final String fileNameEncoding = SftpFileSystemConfigBuilder.getInstance()
228 .getFileNameEncoding(getFileSystemOptions());
229
230 if (fileNameEncoding != null) {
231 try {
232 channel.setFilenameEncoding(fileNameEncoding);
233 } catch (final SftpException e) {
234 throw new FileSystemException("vfs.provider.sftp/filename-encoding.error", fileNameEncoding);
235 }
236 }
237 return channel;
238 } catch (final JSchException e) {
239 throw new FileSystemException("vfs.provider.sftp/connect.error", getRootName(), e);
240 }
241 }
242
243
244
245
246
247
248
249
250
251 public int[] getGroupsIds() throws JSchException, IOException {
252 if (groupsIds == null) {
253 synchronized (this) {
254
255 if (groupsIds == null) {
256 final StringBuilder output = new StringBuilder();
257 final int code = executeCommand("id -G", output);
258 if (code != 0) {
259 throw new JSchException(
260 "Could not get the groups id of the current user (error code: " + code + ")");
261 }
262
263 final String[] groups = output.toString().trim().split("\\s+");
264
265 final int[] groupsIds = new int[groups.length];
266 for (int i = 0; i < groups.length; i++) {
267 groupsIds[i] = Integer.parseInt(groups[i]);
268 }
269 this.groupsIds = groupsIds;
270
271 }
272 }
273 }
274 return groupsIds;
275 }
276
277
278
279
280
281
282 @Override
283 public double getLastModTimeAccuracy() {
284 return LAST_MOD_TIME_ACCURACY;
285 }
286
287
288
289
290
291
292 private Session getSession() throws FileSystemException {
293 if (!this.session.isConnected()) {
294 synchronized (this) {
295 if (!this.session.isConnected()) {
296 doCloseCommunicationLink();
297 this.session = SftpFileProvider.createSession((GenericFileName) getRootName(),
298 getFileSystemOptions());
299 }
300 }
301 }
302 return this.session;
303 }
304
305
306
307
308
309
310
311
312
313 public int getUId() throws JSchException, IOException {
314 if (uid == UNIDENTIFED) {
315 synchronized (this) {
316 if (uid == UNIDENTIFED) {
317 final StringBuilder output = new StringBuilder();
318 final int code = executeCommand("id -u", output);
319 if (code != 0) {
320 throw new FileSystemException(
321 "Could not get the user id of the current user (error code: " + code + ")");
322 }
323 final String uidString = output.toString().trim();
324 try {
325 uid = Integer.parseInt(uidString);
326 } catch (final NumberFormatException e) {
327 LOG.debug("Cannot convert UID to integer: '" + uidString + "'", e);
328 }
329 }
330 }
331 }
332 return uid;
333 }
334
335
336
337
338
339 public boolean isExecDisabled() {
340 return execDisabled;
341 }
342
343
344
345
346
347
348 protected void putChannel(final ChannelSftp channelSftp) {
349 if (idleChannel == null) {
350 synchronized (this) {
351 if (idleChannel == null) {
352
353 if (channelSftp.isConnected() && !channelSftp.isClosed()) {
354 idleChannel = channelSftp;
355 }
356 } else {
357 channelSftp.disconnect();
358 }
359 }
360 } else {
361 channelSftp.disconnect();
362 }
363 }
364
365 }