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.InputStream;
21 import java.io.OutputStream;
22 import java.util.ArrayList;
23 import java.util.Iterator;
24 import java.util.Vector;
25
26 import org.apache.commons.vfs2.FileNotFoundException;
27 import org.apache.commons.vfs2.FileObject;
28 import org.apache.commons.vfs2.FileSystemException;
29 import org.apache.commons.vfs2.FileType;
30 import org.apache.commons.vfs2.NameScope;
31 import org.apache.commons.vfs2.RandomAccessContent;
32 import org.apache.commons.vfs2.VFS;
33 import org.apache.commons.vfs2.provider.AbstractFileName;
34 import org.apache.commons.vfs2.provider.AbstractFileObject;
35 import org.apache.commons.vfs2.provider.UriParser;
36 import org.apache.commons.vfs2.util.FileObjectUtils;
37 import org.apache.commons.vfs2.util.MonitorInputStream;
38 import org.apache.commons.vfs2.util.MonitorOutputStream;
39 import org.apache.commons.vfs2.util.PosixPermissions;
40 import org.apache.commons.vfs2.util.RandomAccessMode;
41
42 import com.jcraft.jsch.ChannelSftp;
43 import com.jcraft.jsch.ChannelSftp.LsEntry;
44 import com.jcraft.jsch.SftpATTRS;
45 import com.jcraft.jsch.SftpException;
46
47
48
49
50 public class SftpFileObject extends AbstractFileObject<SftpFileSystem> {
51
52
53
54
55 private final class SftpInputStream extends MonitorInputStream {
56 private final ChannelSftp channel;
57
58 SftpInputStream(final ChannelSftp channel, final InputStream in) {
59 super(in);
60 this.channel = channel;
61 }
62
63 SftpInputStream(final ChannelSftp channel, final InputStream in, final int bufferSize) {
64 super(in, bufferSize);
65 this.channel = channel;
66 }
67
68
69
70
71 @Override
72 protected void onClose() throws IOException {
73 putChannel(channel);
74 }
75 }
76
77
78
79
80 private final class SftpOutputStream extends MonitorOutputStream {
81 private final ChannelSftp channel;
82
83 SftpOutputStream(final ChannelSftp channel, final OutputStream out) {
84 super(out);
85 this.channel = channel;
86 }
87
88
89
90
91 @Override
92 protected void onClose() throws IOException {
93 putChannel(channel);
94 }
95 }
96 private static final long MOD_TIME_FACTOR = 1000L;
97
98 private SftpATTRS attrs;
99
100 private final String relPath;
101
102
103
104
105
106
107
108
109 protected SftpFileObject(final AbstractFileName fileName, final SftpFileSystem fileSystem) throws FileSystemException {
110 super(fileName, fileSystem);
111 relPath = UriParser.decode(fileSystem.getRootName().getRelativeName(fileName));
112 }
113
114
115
116
117 @Override
118 protected void doCreateFolder() throws Exception {
119 final ChannelSftp channel = getAbstractFileSystem().getChannel();
120 try {
121 channel.mkdir(relPath);
122 } finally {
123 putChannel(channel);
124 }
125 }
126
127
128
129
130 @Override
131 protected void doDelete() throws Exception {
132 final ChannelSftp channel = getAbstractFileSystem().getChannel();
133 try {
134 if (isFile()) {
135 channel.rm(relPath);
136 } else {
137 channel.rmdir(relPath);
138 }
139 } finally {
140 putChannel(channel);
141 }
142 }
143
144
145 @Override
146 protected synchronized void doDetach() throws Exception {
147 attrs = null;
148 }
149
150
151
152
153 @Override
154 protected synchronized long doGetContentSize() throws Exception {
155 if (attrs == null || (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_SIZE) == 0) {
156 throw new FileSystemException("vfs.provider.sftp/unknown-size.error");
157 }
158 return attrs.getSize();
159 }
160
161
162
163
164 @SuppressWarnings("resource")
165 @Override
166 protected InputStream doGetInputStream(final int bufferSize) throws Exception {
167
168 synchronized (getAbstractFileSystem()) {
169 final ChannelSftp channel = getAbstractFileSystem().getChannel();
170
171
172
173
174
175
176
177
178
179
180
181 final InputStream inputStream;
182 try {
183
184
185 if (!getType().hasContent()) {
186
187 putChannel(channel);
188 throw new FileSystemException("vfs.provider/read-not-file.error", getName());
189 }
190 inputStream = channel.get(relPath);
191 } catch (final SftpException e) {
192 putChannel(channel);
193 if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
194 throw new FileNotFoundException(getName());
195 }
196 throw new FileSystemException(e);
197 }
198 return new SftpInputStream(channel, inputStream, bufferSize);
199 }
200 }
201
202 @Override
203 protected synchronized long doGetLastModifiedTime() throws Exception {
204 if (attrs == null || (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_ACMODTIME) == 0) {
205 throw new FileSystemException("vfs.provider.sftp/unknown-modtime.error");
206 }
207 return attrs.getMTime() * MOD_TIME_FACTOR;
208 }
209
210
211
212
213 @Override
214 protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
215
216
217
218
219
220
221 final ChannelSftp channel = getAbstractFileSystem().getChannel();
222 try {
223 return new SftpOutputStream(channel, channel.put(relPath, bAppend ? ChannelSftp.APPEND : ChannelSftp.OVERWRITE));
224 } catch (final Exception ex) {
225 putChannel(channel);
226 throw ex;
227 }
228
229 }
230
231 @Override
232 protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
233 return new SftpRandomAccessContent(this, mode);
234 }
235
236
237
238
239 @Override
240 protected synchronized FileType doGetType() throws Exception {
241 if (attrs == null) {
242 statSelf();
243 }
244
245 if (attrs == null) {
246 return FileType.IMAGINARY;
247 }
248
249 if ((attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_PERMISSIONS) == 0) {
250 throw new FileSystemException("vfs.provider.sftp/unknown-permissions.error");
251 }
252 if (attrs.isDir()) {
253 return FileType.FOLDER;
254 }
255 return FileType.FILE;
256 }
257
258 @Override
259 protected boolean doIsExecutable() throws Exception {
260 return getPermissions(true).isExecutable();
261 }
262
263 @Override
264 protected boolean doIsReadable() throws Exception {
265 return getPermissions(true).isReadable();
266 }
267
268 @Override
269 protected boolean doIsWriteable() throws Exception {
270 return getPermissions(true).isWritable();
271 }
272
273
274
275
276 @Override
277 protected String[] doListChildren() throws Exception {
278
279 return null;
280 }
281
282
283
284
285 @Override
286 protected FileObject[] doListChildrenResolved() throws Exception {
287
288 if (isFile()) {
289 return null;
290 }
291
292 Vector<?> vector = null;
293 final ChannelSftp channel = getAbstractFileSystem().getChannel();
294
295 try {
296
297 vector = channel.ls(relPath);
298 } catch (final SftpException e) {
299 String workingDirectory = null;
300 try {
301 if (relPath != null) {
302 workingDirectory = channel.pwd();
303 channel.cd(relPath);
304 }
305 } catch (final SftpException ex) {
306
307 return null;
308 }
309
310 SftpException lsEx = null;
311 try {
312 vector = channel.ls(".");
313 } catch (final SftpException ex) {
314 lsEx = ex;
315 } finally {
316 try {
317 if (relPath != null) {
318 channel.cd(workingDirectory);
319 }
320 } catch (final SftpException xe) {
321 throw new FileSystemException("vfs.provider.sftp/change-work-directory-back.error",
322 workingDirectory, lsEx);
323 }
324 }
325
326 if (lsEx != null) {
327 throw lsEx;
328 }
329 } finally {
330 putChannel(channel);
331 }
332 FileSystemException.requireNonNull(vector, "vfs.provider.sftp/list-children.error");
333
334
335 final ArrayList<FileObject> children = new ArrayList<>();
336 for (@SuppressWarnings("unchecked")
337 final Iterator<LsEntry> iterator = (Iterator<LsEntry>) vector.iterator(); iterator.hasNext();) {
338 final LsEntry stat = iterator.next();
339
340 String name = stat.getFilename();
341 if (VFS.isUriStyle() && stat.getAttrs().isDir() && name.charAt(name.length() - 1) != '/') {
342 name += "/";
343 }
344
345 if (name.equals(".") || name.equals("..") || name.equals("./") || name.equals("../")) {
346 continue;
347 }
348
349 final FileObject fo = getFileSystem().resolveFile(getFileSystem().getFileSystemManager()
350 .resolveName(getName(), UriParser.encode(name), NameScope.CHILD));
351
352 ((SftpFileObject) FileObjectUtils.getAbstractFileObject(fo)).setStat(stat.getAttrs());
353
354 children.add(fo);
355 }
356
357 return children.toArray(EMPTY_ARRAY);
358 }
359
360
361
362
363 @Override
364 protected void doRename(final FileObject newFile) throws Exception {
365 final ChannelSftp channel = getAbstractFileSystem().getChannel();
366 try {
367 final SftpFileObject newSftpFileObject = (SftpFileObject) FileObjectUtils.getAbstractFileObject(newFile);
368 channel.rename(relPath, newSftpFileObject.relPath);
369 } finally {
370 putChannel(channel);
371 }
372 }
373
374 @Override
375 protected synchronized boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception {
376 final PosixPermissions permissions = getPermissions(false);
377 final int newPermissions = permissions.makeExecutable(executable, ownerOnly);
378 if (newPermissions == permissions.getPermissions()) {
379 return true;
380 }
381
382 attrs.setPERMISSIONS(newPermissions);
383 flushStat();
384
385 return true;
386 }
387
388
389
390
391
392
393
394
395 @Override
396 protected synchronized boolean doSetLastModifiedTime(final long modtime) throws Exception {
397 final int newMTime = (int) (modtime / MOD_TIME_FACTOR);
398 attrs.setACMODTIME(attrs.getATime(), newMTime);
399 flushStat();
400 return true;
401 }
402
403 @Override
404 protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception {
405 final PosixPermissions permissions = getPermissions(false);
406 final int newPermissions = permissions.makeReadable(readable, ownerOnly);
407 if (newPermissions == permissions.getPermissions()) {
408 return true;
409 }
410
411 attrs.setPERMISSIONS(newPermissions);
412 flushStat();
413
414 return true;
415 }
416
417 @Override
418 protected synchronized boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception {
419 final PosixPermissions permissions = getPermissions(false);
420 final int newPermissions = permissions.makeWritable(writable, ownerOnly);
421 if (newPermissions == permissions.getPermissions()) {
422 return true;
423 }
424
425 attrs.setPERMISSIONS(newPermissions);
426 flushStat();
427
428 return true;
429 }
430
431 private synchronized void flushStat() throws IOException, SftpException {
432 final ChannelSftp channel = getAbstractFileSystem().getChannel();
433 try {
434 channel.setStat(relPath, attrs);
435 } finally {
436 putChannel(channel);
437 }
438 }
439
440
441
442
443
444 InputStream getInputStream(final long filePointer) throws IOException {
445 final ChannelSftp channel = getAbstractFileSystem().getChannel();
446
447
448 try {
449 return new SftpInputStream(channel, channel.get(getName().getPathDecoded(), null, filePointer));
450 } catch (final SftpException e) {
451 putChannel(channel);
452 throw new FileSystemException(e);
453 }
454 }
455
456
457
458
459
460
461
462
463
464 protected synchronized PosixPermissions getPermissions(final boolean checkIds) throws Exception {
465 statSelf();
466 boolean isInGroup = false;
467 if (checkIds) {
468 if (getAbstractFileSystem().isExecDisabled()) {
469
470
471
472 return new UserIsOwnerPosixPermissions(attrs.getPermissions());
473 }
474
475 for (final int groupId : getAbstractFileSystem().getGroupsIds()) {
476 if (groupId == attrs.getGId()) {
477 isInGroup = true;
478 break;
479 }
480 }
481 }
482 final boolean isOwner = checkIds && attrs.getUId() == getAbstractFileSystem().getUId();
483 return new PosixPermissions(attrs.getPermissions(), isOwner, isInGroup);
484 }
485
486
487
488
489 @Override
490 protected void onChange() throws Exception {
491 statSelf();
492 }
493
494 @SuppressWarnings("resource")
495 private void putChannel(final ChannelSftp channel) {
496 getAbstractFileSystem().putChannel(channel);
497 }
498
499
500
501
502 private synchronized void setStat(final SftpATTRS attrs) {
503 this.attrs = attrs;
504 }
505
506
507
508
509
510
511 private synchronized void statSelf() throws IOException {
512 ChannelSftp channelSftp = null;
513 try {
514 channelSftp = getAbstractFileSystem().getChannel();
515 setStat(channelSftp.stat(relPath));
516 } catch (final SftpException e) {
517 try {
518
519 if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE) {
520 channelSftp.disconnect();
521 channelSftp = getAbstractFileSystem().getChannel();
522 setStat(channelSftp.stat(relPath));
523 } else {
524
525 attrs = null;
526 }
527 } catch (final SftpException innerEx) {
528
529
530
531
532
533
534
535
536 attrs = null;
537 }
538 } finally {
539 if (channelSftp != null) {
540 putChannel(channelSftp);
541 }
542 }
543 }
544
545 }