You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2013/07/22 20:37:04 UTC

[7/9] git commit: Provide an SFTP client

Provide an SFTP client

Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/21c1cee9
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/21c1cee9
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/21c1cee9

Branch: refs/heads/master
Commit: 21c1cee99ba981dd262f7911617160de0c8c6654
Parents: 16fc6a8
Author: Guillaume Nodet <gn...@apache.org>
Authored: Mon Jul 22 16:07:29 2013 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Jul 22 16:07:29 2013 +0200

----------------------------------------------------------------------
 .../java/org/apache/sshd/ClientSession.java     |   6 +
 .../java/org/apache/sshd/client/ScpClient.java  |  18 +
 .../java/org/apache/sshd/client/SftpClient.java | 177 +++++
 .../client/channel/AbstractClientChannel.java   |   2 +-
 .../sshd/client/scp/DefaultScpClient.java       |  19 +
 .../sshd/client/session/ClientSessionImpl.java  |   6 +
 .../sshd/client/sftp/DefaultSftpClient.java     | 703 +++++++++++++++++++
 .../org/apache/sshd/common/file/SshFile.java    |  26 +
 .../common/file/nativefs/NativeSshFile.java     | 120 ++++
 .../apache/sshd/server/sftp/SftpSubsystem.java  | 623 +++++-----------
 .../src/test/java/org/apache/sshd/SftpTest.java |  61 ++
 .../sshd/sftp/reply/SshFxpStatusReply.java      |  35 +-
 .../java/org/apache/sshd/sftp/SftpTest.java     |  11 +
 13 files changed, 1328 insertions(+), 479 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/21c1cee9/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
index 6b7b1af..26fd46b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
@@ -24,6 +24,7 @@ import java.util.Map;
 
 import org.apache.sshd.client.ClientFactoryManager;
 import org.apache.sshd.client.ScpClient;
+import org.apache.sshd.client.SftpClient;
 import org.apache.sshd.client.channel.ChannelDirectTcpip;
 import org.apache.sshd.client.channel.ChannelExec;
 import org.apache.sshd.client.channel.ChannelShell;
@@ -118,6 +119,11 @@ public interface ClientSession extends Session {
     ScpClient createScpClient();
 
     /**
+     * Create an SFTP client from this session.
+     */
+    SftpClient createSftpClient() throws IOException;
+
+    /**
      * Start forwarding the given local address on the client to the given address on the server.
      */
     SshdSocketAddress startLocalPortForwarding(SshdSocketAddress local, SshdSocketAddress remote) throws IOException;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/21c1cee9/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
index fc6bfb8..d2bdaa8 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 package org.apache.sshd.client;
 
 import java.io.IOException;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/21c1cee9/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
new file mode 100644
index 0000000..5150a81
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.EnumSet;
+
+/**
+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ */
+public interface SftpClient {
+
+    int S_IFMT =   0170000;  // bitmask for the file type bitfields
+    int S_IFSOCK = 0140000;  // socket
+    int S_IFLNK =  0120000;  // symbolic link
+    int S_IFREG =  0100000;  // regular file
+    int S_IFBLK =  0060000;  // block device
+    int S_IFDIR =  0040000;  // directory
+    int S_IFCHR =  0020000;  // character device
+    int S_IFIFO =  0010000;  // fifo
+    int S_ISUID =  0004000;  // set UID bit
+    int S_ISGID =  0002000;  // set GID bit
+    int S_ISVTX =  0001000;  // sticky bit
+    int S_IRUSR =  0000400;
+    int S_IWUSR =  0000200;
+    int S_IXUSR =  0000100;
+    int S_IRGRP =  0000040;
+    int S_IWGRP =  0000020;
+    int S_IXGRP =  0000010;
+    int S_IROTH =  0000004;
+    int S_IWOTH =  0000002;
+    int S_IXOTH =  0000001;
+
+    enum OpenMode {
+        Read,
+        Write,
+        Append,
+        Create,
+        Truncate,
+        Exclusive
+    }
+
+    enum Attribute {
+        Size,
+        UidGid,
+        Perms,
+        AcModTime
+    }
+
+    public static class Handle {
+        public final String id;
+        public Handle(String id) {
+            this.id = id;
+        }
+    }
+
+    public static class Attributes {
+        public EnumSet<Attribute> flags = EnumSet.noneOf(Attribute.class);
+        public long size;
+        public int uid;
+        public int gid;
+        public int perms;
+        public int atime;
+        public int mtime;
+        public Attributes size(long size) {
+            flags.add(Attribute.Size);
+            this.size = size;
+            return this;
+        }
+        public Attributes owner(int uid, int gid) {
+            flags.add(Attribute.UidGid);
+            this.uid = uid;
+            this.gid = gid;
+            return this;
+        }
+        public Attributes perms(int perms) {
+            flags.add(Attribute.Perms);
+            this.perms = perms;
+            return this;
+        }
+        public Attributes time(int atime, int mtime) {
+            flags.add(Attribute.AcModTime);
+            this.atime = atime;
+            this.mtime = mtime;
+            return this;
+        }
+        public boolean isDirectory() {
+            return (perms & S_IFMT) == S_IFDIR;
+        }
+        public boolean isRegularFile() {
+            return (perms & S_IFMT) == S_IFREG;
+        }
+        public boolean isSymlink() {
+            return (perms & S_IFMT) == S_IFLNK;
+        }
+    }
+
+    public static class DirEntry {
+        public final String filename;
+        public final String longFilename;
+        public final Attributes attributes;
+        public DirEntry(String filename, String longFilename, Attributes attributes) {
+            this.filename = filename;
+            this.longFilename = longFilename;
+            this.attributes = attributes;
+        }
+    }
+
+    //
+    // Low level API
+    //
+
+    Handle open(String path, EnumSet<OpenMode> options) throws IOException;
+
+    void close(Handle handle) throws IOException;
+
+    void remove(String path) throws IOException;
+
+    void rename(String oldPath, String newPath) throws IOException;
+
+    int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException;
+
+    void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException;
+
+    void mkdir(String path) throws IOException;
+
+    void rmdir(String path) throws IOException;
+
+    Handle openDir(String path) throws IOException;
+
+    DirEntry[] readDir(Handle handle) throws IOException;
+
+    String canonicalPath(String canonical) throws IOException;
+
+    Attributes stat(String path) throws IOException;
+
+    Attributes lstat(String path) throws IOException;
+
+    Attributes stat(Handle handle) throws IOException;
+
+    void setStat(String path, Attributes attributes) throws IOException;
+
+    void setStat(Handle handle, Attributes attributes) throws IOException;
+
+    String readLink(String path) throws IOException;
+
+    void symLink(String linkPath, String targetPath) throws IOException;
+
+    //
+    // High level API
+    //
+
+    Iterable<DirEntry> readDir(String path) throws IOException;
+
+    InputStream read(String path) throws IOException;
+
+    OutputStream write(String path) throws IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/21c1cee9/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
index 40015c3..eb2ea17 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
@@ -127,7 +127,7 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C
     @Override
     protected void doClose() {
         super.doClose();
-        IoUtils.closeQuietly(invertedIn, in, out, err);
+        IoUtils.closeQuietly(invertedIn, invertedOut, invertedErr, in, out, err);
     }
 
     public int waitFor(int mask, long timeout) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/21c1cee9/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
index 2bd6985..1b590c2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 package org.apache.sshd.client.scp;
 
 import java.io.IOException;
@@ -14,6 +32,7 @@ import org.apache.sshd.common.file.SshFile;
 import org.apache.sshd.common.scp.ScpHelper;
 
 /**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class DefaultScpClient implements ScpClient {
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/21c1cee9/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index 10825b7..d40173f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -31,6 +31,7 @@ import org.apache.sshd.ClientSession;
 import org.apache.sshd.client.ClientFactoryManager;
 import org.apache.sshd.client.ScpClient;
 import org.apache.sshd.client.ServerKeyVerifier;
+import org.apache.sshd.client.SftpClient;
 import org.apache.sshd.client.UserAuth;
 import org.apache.sshd.client.UserInteraction;
 import org.apache.sshd.client.auth.UserAuthAgent;
@@ -45,6 +46,7 @@ import org.apache.sshd.client.future.AuthFuture;
 import org.apache.sshd.client.future.DefaultAuthFuture;
 import org.apache.sshd.client.future.OpenFuture;
 import org.apache.sshd.client.scp.DefaultScpClient;
+import org.apache.sshd.client.sftp.DefaultSftpClient;
 import org.apache.sshd.common.Channel;
 import org.apache.sshd.common.KeyExchange;
 import org.apache.sshd.common.KeyPairProvider;
@@ -256,6 +258,10 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
         return new DefaultScpClient(this);
     }
 
+    public SftpClient createSftpClient() throws IOException {
+        return new DefaultSftpClient(this);
+    }
+
     public SshdSocketAddress startLocalPortForwarding(SshdSocketAddress local, SshdSocketAddress remote) throws IOException {
         return getTcpipForwarder().startLocalPortForwarding(local, remote);
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/21c1cee9/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
new file mode 100644
index 0000000..1dd6351
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
@@ -0,0 +1,703 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.sftp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.client.SftpClient;
+import org.apache.sshd.client.channel.ChannelSubsystem;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.util.Buffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultSftpClient implements SftpClient {
+
+    public static final int SSH_FXP_INIT =             1;
+    public static final int SSH_FXP_VERSION =          2;
+    public static final int SSH_FXP_OPEN =             3;
+    public static final int SSH_FXP_CLOSE =            4;
+    public static final int SSH_FXP_READ =             5;
+    public static final int SSH_FXP_WRITE =            6;
+    public static final int SSH_FXP_LSTAT =            7;
+    public static final int SSH_FXP_FSTAT =            8;
+    public static final int SSH_FXP_SETSTAT =          9;
+    public static final int SSH_FXP_FSETSTAT =        10;
+    public static final int SSH_FXP_OPENDIR =         11;
+    public static final int SSH_FXP_READDIR =         12;
+    public static final int SSH_FXP_REMOVE =          13;
+    public static final int SSH_FXP_MKDIR =           14;
+    public static final int SSH_FXP_RMDIR =           15;
+    public static final int SSH_FXP_REALPATH =        16;
+    public static final int SSH_FXP_STAT =            17;
+    public static final int SSH_FXP_RENAME =          18;
+    public static final int SSH_FXP_READLINK =        19;
+    public static final int SSH_FXP_SYMLINK =         20;
+    public static final int SSH_FXP_STATUS =         101;
+    public static final int SSH_FXP_HANDLE =         102;
+    public static final int SSH_FXP_DATA =           103;
+    public static final int SSH_FXP_NAME =           104;
+    public static final int SSH_FXP_ATTRS =          105;
+    public static final int SSH_FXP_EXTENDED =       200;
+    public static final int SSH_FXP_EXTENDED_REPLY = 201;
+
+    public static final int SSH_FX_OK =                0;
+    public static final int SSH_FX_EOF =               1;
+    public static final int SSH_FX_NO_SUCH_FILE =      2;
+    public static final int SSH_FX_PERMISSION_DENIED = 3;
+    public static final int SSH_FX_FAILURE =           4;
+    public static final int SSH_FX_BAD_MESSAGE =       5;
+    public static final int SSH_FX_NO_CONNECTION =     6;
+    public static final int SSH_FX_CONNECTION_LOST =   7;
+    public static final int SSH_FX_OP_UNSUPPORTED =    8;
+
+    public static final int SSH_FILEXFER_ATTR_SIZE =        0x00000001;
+    public static final int SSH_FILEXFER_ATTR_UIDGID =      0x00000002;
+    public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004;
+    public static final int SSH_FILEXFER_ATTR_ACMODTIME =   0x00000008; //v3 naming convention
+    public static final int SSH_FILEXFER_ATTR_EXTENDED =    0x80000000;
+
+    public static final int SSH_FXF_READ =   0x00000001;
+    public static final int SSH_FXF_WRITE =  0x00000002;
+    public static final int SSH_FXF_APPEND = 0x00000004;
+    public static final int SSH_FXF_CREAT =  0x00000008;
+    public static final int SSH_FXF_TRUNC =  0x00000010;
+    public static final int SSH_FXF_EXCL =   0x00000020;
+
+    private final ClientSession clientSession;
+    private final ChannelSubsystem channel;
+    private final Map<Integer, Buffer> messages;
+    private final AtomicInteger cmdId = new AtomicInteger(100);
+    private final Buffer receiveBuffer = new Buffer();
+
+    public DefaultSftpClient(ClientSession clientSession) throws IOException {
+        this.clientSession = clientSession;
+        this.channel = clientSession.createSubsystemChannel("sftp");
+        this.messages = new HashMap<Integer, Buffer>();
+        try {
+            this.channel.setOut(new OutputStream() {
+                @Override
+                public void write(int b) throws IOException {
+                    write(new byte[] { (byte) b }, 0, 1);
+                }
+                @Override
+                public void write(byte[] b, int off, int len) throws IOException {
+                    data(b, off, len);
+                }
+            });
+            this.channel.setErr(new ByteArrayOutputStream());
+            this.channel.open().await();
+        } catch (InterruptedException e) {
+            throw (IOException) new InterruptedIOException().initCause(e);
+        }
+        init();
+    }
+
+    /**
+     * Receive binary data
+     */
+    protected int data(byte[] buf, int start, int len) throws IOException {
+        Buffer incoming = new Buffer(buf,  start, len);
+        // If we already have partial data, we need to append it to the buffer and use it
+        if (receiveBuffer.available() > 0) {
+            receiveBuffer.putBuffer(incoming);
+            incoming = receiveBuffer;
+        }
+        // Process commands
+        int rpos = incoming.rpos();
+        while (receive(incoming));
+        int read = incoming.rpos() - rpos;
+        // Compact and add remaining data
+        receiveBuffer.compact();
+        if (receiveBuffer != incoming && incoming.available() > 0) {
+            receiveBuffer.putBuffer(incoming);
+        }
+        return read;
+    }
+
+    /**
+     * Read SFTP packets from buffer
+     */
+    protected boolean receive(Buffer incoming) throws IOException {
+        int rpos = incoming.rpos();
+        int wpos = incoming.wpos();
+        if (wpos - rpos > 4) {
+            int length = incoming.getInt();
+            if (length < 5) {
+                throw new IOException("Illegal sftp packet length: " + length);
+            }
+            if (wpos - rpos >= length + 4) {
+                incoming.rpos(rpos);
+                incoming.wpos(rpos + 4 + length);
+                process(incoming);
+                incoming.rpos(rpos + 4 + length);
+                incoming.wpos(wpos);
+                return true;
+            }
+        }
+        incoming.rpos(rpos);
+        return false;
+    }
+
+    /**
+     * Process an SFTP packet
+     */
+    protected void process(Buffer incoming) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putBuffer(incoming);
+        buffer.rpos(5);
+        int id = buffer.getInt();
+        buffer.rpos(0);
+        synchronized (messages) {
+            messages.put(id, buffer);
+            messages.notifyAll();
+        }
+    }
+
+
+    protected int send(int cmd, Buffer buffer) throws IOException {
+        int id = cmdId.incrementAndGet();
+        DataOutputStream dos = new DataOutputStream(channel.getInvertedIn());
+        dos.writeInt(5 + buffer.available());
+        dos.writeByte(cmd);
+        dos.writeInt(id);
+        dos.write(buffer.array(), buffer.rpos(), buffer.available());
+        dos.flush();
+        return id;
+    }
+
+    protected Buffer receive(int id) throws IOException {
+        synchronized (messages) {
+            while (true) {
+                Buffer buffer = messages.get(id);
+                if (buffer != null) {
+                    return buffer;
+                }
+                try {
+                    messages.wait();
+                } catch (InterruptedException e) {
+                    throw (IOException) new InterruptedIOException().initCause(e);
+                }
+            }
+        }
+    }
+
+    protected Buffer read() throws IOException {
+        DataInputStream dis = new DataInputStream(channel.getInvertedOut());
+        int length = dis.readInt();
+        if (length < 5) {
+            throw new IllegalArgumentException();
+        }
+        Buffer buffer = new Buffer(length + 4);
+        buffer.putInt(length);
+        int nb = length;
+        while (nb > 0) {
+            int l = dis.read(buffer.array(), buffer.wpos(), nb);
+            if (l < 0) {
+                throw new IllegalArgumentException();
+            }
+            buffer.wpos(buffer.wpos() + l);
+            nb -= l;
+        }
+        return buffer;
+    }
+
+    protected void init() throws IOException {
+        // Init packet
+        DataOutputStream dos = new DataOutputStream(channel.getInvertedIn());
+        dos.writeInt(5);
+        dos.writeByte(SSH_FXP_INIT);
+        dos.writeInt(3);
+        dos.flush();
+        Buffer buffer = null;
+        synchronized (messages) {
+            while (messages.isEmpty()) {
+                try {
+                    messages.wait();
+                } catch (InterruptedException e) {
+                    throw (IOException) new InterruptedIOException().initCause(e);
+                }
+            }
+            buffer = messages.remove(messages.keySet().iterator().next());
+
+        }
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_VERSION) {
+            if (id != 3) {
+                throw new SshException("Unable to use SFTP v3, server replied with version " + id);
+            }
+        } else if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            throw new SshException("SFTP error (" + substatus + "): " + msg);
+        } else {
+            throw new SshException("Unexpected SFTP packet received: " + type);
+        }
+    }
+
+    protected void checkStatus(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (substatus != SSH_FX_OK) {
+                throw new SshException("SFTP error (" + substatus + "): " + msg);
+            }
+        } else {
+            throw new SshException("Unexpected SFTP packet received: " + type);
+        }
+    }
+
+    protected Handle checkHandle(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            throw new SshException("SFTP error (" + substatus + "): " + msg);
+        } else if (type == SSH_FXP_HANDLE) {
+            String handle = buffer.getString();
+            return new Handle(handle);
+        } else {
+            throw new SshException("Unexpected SFTP packet received: " + type);
+        }
+    }
+
+    protected Attributes checkAttributes(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            throw new SshException("SFTP error (" + substatus + "): " + msg);
+        } else if (type == SSH_FXP_ATTRS) {
+            return readAttributes(buffer);
+        } else {
+            throw new SshException("Unexpected SFTP packet received: " + type);
+        }
+    }
+
+    protected String checkOneName(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            throw new SshException("SFTP error (" + substatus + "): " + msg);
+        } else if (type == SSH_FXP_NAME) {
+            int len = buffer.getInt();
+            if (len != 1) {
+                throw new SshException("SFTP error: received " + len + " names instead of 1");
+            }
+            String name = buffer.getString();
+            String longName = buffer.getString();
+            Attributes attrs = readAttributes(buffer);
+            return name;
+        } else {
+            throw new SshException("Unexpected SFTP packet received: " + type);
+        }
+    }
+
+    protected Attributes readAttributes(Buffer buffer) throws IOException {
+        Attributes attrs = new Attributes();
+        int flags = buffer.getInt();
+        if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+            attrs.flags.add(Attribute.Size);
+            attrs.size = buffer.getLong();
+        }
+        if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+            attrs.flags.add(Attribute.UidGid);
+            attrs.uid = buffer.getInt();
+            attrs.gid = buffer.getInt();
+        }
+        if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+            attrs.flags.add(Attribute.Perms);
+            attrs.perms = buffer.getInt();
+        }
+        if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+            attrs.flags.add(Attribute.AcModTime);
+            attrs.atime = buffer.getInt();
+            attrs.mtime = buffer.getInt();
+        }
+        return attrs;
+    }
+
+    protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException {
+        int flags = 0;
+        for (Attribute a : attributes.flags) {
+            switch (a) {
+                case Size:      flags |= SSH_FILEXFER_ATTR_SIZE; break;
+                case UidGid:    flags |= SSH_FILEXFER_ATTR_UIDGID; break;
+                case Perms:     flags |= SSH_FILEXFER_ATTR_PERMISSIONS; break;
+                case AcModTime: flags |= SSH_FILEXFER_ATTR_ACMODTIME; break;
+            }
+        }
+        buffer.putInt(flags);
+        if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+            buffer.putLong(attributes.size);
+        }
+        if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+            buffer.putInt(attributes.uid);
+            buffer.putInt(attributes.gid);
+        }
+        if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+            buffer.putInt(attributes.perms);
+        }
+        if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+            buffer.putInt(attributes.atime);
+            buffer.putInt(attributes.mtime);
+        }
+    }
+
+    public Handle open(String path, EnumSet<OpenMode> options) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(path);
+        int mode = 0;
+        for (OpenMode m : options) {
+            switch (m) {
+                case Read:      mode |= SSH_FXF_READ; break;
+                case Write:     mode |= SSH_FXF_WRITE; break;
+                case Append:    mode |= SSH_FXF_APPEND; break;
+                case Create:    mode |= SSH_FXF_CREAT; break;
+                case Truncate:  mode |= SSH_FXF_TRUNC; break;
+                case Exclusive: mode |= SSH_FXF_EXCL; break;
+            }
+        }
+        buffer.putInt(mode);
+        return checkHandle(receive(send(SSH_FXP_OPEN, buffer)));
+    }
+
+    public void close(Handle handle) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(handle.id);
+        checkStatus(receive(send(SSH_FXP_CLOSE, buffer)));
+    }
+
+    public void remove(String path) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(path);
+        checkStatus(receive(send(SSH_FXP_REMOVE, buffer)));
+    }
+
+    public void rename(String oldPath, String newPath) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(oldPath);
+        buffer.putString(newPath);
+        checkStatus(receive(send(SSH_FXP_RENAME, buffer)));
+    }
+
+    public int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(handle.id);
+        buffer.putLong(fileOffset);
+        buffer.putInt(len);
+        buffer = receive(send(SSH_FXP_READ, buffer));
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (substatus == SSH_FX_EOF) {
+                return -1;
+            }
+            throw new SshException("SFTP error (" + substatus + "): " + msg);
+        } else if (type == SSH_FXP_DATA) {
+            len = buffer.getInt();
+            buffer.getRawBytes(dst, dstoff, len);
+            return len;
+        } else {
+            throw new SshException("Unexpected SFTP packet received: " + type);
+        }
+    }
+
+    public void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(handle.id);
+        buffer.putLong(fileOffset);
+        buffer.putBytes(src, srcoff, len);
+        checkStatus(receive(send(SSH_FXP_WRITE, buffer)));
+    }
+
+    public void mkdir(String path) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(path);
+        checkStatus(receive(send(SSH_FXP_MKDIR, buffer)));
+    }
+
+    public void rmdir(String path) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(path);
+        checkStatus(receive(send(SSH_FXP_RMDIR, buffer)));
+    }
+
+    public Handle openDir(String path) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(path);
+        return checkHandle(receive(send(SSH_FXP_OPENDIR, buffer)));
+    }
+
+    public DirEntry[] readDir(Handle handle) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(handle.id);
+        buffer = receive(send(SSH_FXP_READDIR, buffer));
+        int length = buffer.getInt();
+        int type = buffer.getByte();
+        int id = buffer.getInt();
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (substatus == SSH_FX_EOF) {
+                return null;
+            }
+            throw new SshException("SFTP error (" + substatus + "): " + msg);
+        } else if (type == SSH_FXP_NAME) {
+            int len = buffer.getInt();
+            DirEntry[] entries = new DirEntry[len];
+            for (int i = 0; i < len; i++) {
+                String name = buffer.getString();
+                String longName = buffer.getString();
+                Attributes attrs = readAttributes(buffer);
+                entries[i] = new DirEntry(name, longName, attrs);
+            }
+            return entries;
+        } else {
+            throw new SshException("Unexpected SFTP packet received: " + type);
+        }
+    }
+
+    public String canonicalPath(String path) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(path);
+        return checkOneName(receive(send(SSH_FXP_REALPATH, buffer)));
+    }
+
+    public Attributes stat(String path) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(path);
+        return checkAttributes(receive(send(SSH_FXP_STAT, buffer)));
+    }
+
+    public Attributes lstat(String path) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(path);
+        return checkAttributes(receive(send(SSH_FXP_LSTAT, buffer)));
+    }
+
+    public Attributes stat(Handle handle) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(handle.id);
+        return checkAttributes(receive(send(SSH_FXP_FSTAT, buffer)));
+    }
+
+    public void setStat(String path, Attributes attributes) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(path);
+        writeAttributes(buffer, attributes);
+        checkStatus(receive(send(SSH_FXP_SETSTAT, buffer)));
+    }
+
+    public void setStat(Handle handle, Attributes attributes) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(handle.id);
+        writeAttributes(buffer, attributes);
+        checkStatus(receive(send(SSH_FXP_FSETSTAT, buffer)));
+    }
+
+    public String readLink(String path) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(path);
+        return checkOneName(receive(send(SSH_FXP_READLINK, buffer)));
+    }
+
+    public void symLink(String linkPath, String targetPath) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putString(linkPath);
+        buffer.putString(targetPath);
+        checkStatus(receive(send(SSH_FXP_RENAME, buffer)));
+    }
+
+    public Iterable<DirEntry> readDir(final String path) throws IOException {
+        return new Iterable<DirEntry>() {
+            public Iterator<DirEntry> iterator() {
+                return new Iterator<DirEntry>() {
+                    Handle handle;
+                    DirEntry[] entries;
+                    int index;
+                    {
+                        open();
+                        load();
+                    }
+                    public boolean hasNext() {
+                        return entries != null && index < entries.length;
+                    }
+                    public DirEntry next() {
+                        DirEntry entry = entries[index++];
+                        if (index >= entries.length) {
+                            load();
+                        }
+                        return entry;
+                    }
+                    private void open() {
+                        try {
+                            handle = openDir(path);
+                        } catch (IOException e) {
+                            throw new RuntimeException(e);
+                        }
+                    }
+                    private void load() {
+                        try {
+                            entries = readDir(handle);
+                            index = 0;
+                            if (entries == null) {
+                                close(handle);
+                            }
+                        } catch (IOException e) {
+                            entries = null;
+                            try {
+                                close(handle);
+                            } catch (IOException t) {
+                                // Ignore
+                            }
+                            throw new RuntimeException(e);
+                        }
+                    }
+                    public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+        };
+    }
+
+    public InputStream read(final String path) throws IOException {
+        return new InputStream() {
+            byte[] buffer = new byte[32 * 1024];
+            int index = 0;
+            int available = 0;
+            Handle handle = DefaultSftpClient.this.open(path, EnumSet.of(OpenMode.Read));
+            long offset;
+            @Override
+            public int read() throws IOException {
+                byte[] buffer = new byte[1];
+                int read = read(buffer, 0, 1);
+                if (read > 0) {
+                    return buffer[0];
+                }
+                return read;
+            }
+            @Override
+            public int read(byte[] b, int off, int len) throws IOException {
+                int idx = off;
+                while (len > 0) {
+                    if (index >= available) {
+                        available = DefaultSftpClient.this.read(handle, offset, buffer, 0, buffer.length);
+                        if (available < 0) {
+                            if (idx == off) {
+                                return -1;
+                            } else {
+                                break;
+                            }
+                        }
+                        offset += available;
+                        index = 0;
+                    }
+                    if (index >= available) {
+                        break;
+                    }
+                    int nb = Math.min(len, available - index);
+                    System.arraycopy(buffer, index, b, idx, nb);
+                    index += nb;
+                    idx += nb;
+                    len -= nb;
+                }
+                return idx - off;
+            }
+            @Override
+            public void close() throws IOException {
+                DefaultSftpClient.this.close(handle);
+            }
+        };
+    }
+
+    public OutputStream write(final String path) throws IOException {
+        return new OutputStream() {
+            byte[] buffer = new byte[32 * 1024];
+            int index = 0;
+            Handle handle = DefaultSftpClient.this.open(path, EnumSet.of(OpenMode.Write));
+            long offset;
+            @Override
+            public void write(int b) throws IOException {
+                byte[] buffer = new byte[1];
+                buffer[0] = (byte) b;
+                write(buffer, 0, 1);
+            }
+            @Override
+            public void write(byte[] b, int off, int len) throws IOException {
+                do {
+                    int nb = Math.min(len, buffer.length - index);
+                    System.arraycopy(b, off, buffer, index, nb);
+                    index += nb;
+                    if (index == buffer.length) {
+                        flush();
+                    }
+                    len -= nb;
+                } while (len > 0);
+            }
+            @Override
+            public void flush() throws IOException {
+                DefaultSftpClient.this.write(handle, offset, buffer, 0, index);
+                offset += index;
+                index = 0;
+            }
+            @Override
+            public void close() throws IOException {
+                DefaultSftpClient.this.close(handle);
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/21c1cee9/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java b/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java
index 511fe4c..1da1748 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java
@@ -23,6 +23,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * This is the file abstraction used by the server.
@@ -31,6 +33,21 @@ import java.util.List;
  */
 public interface SshFile {
 
+    enum Attribute {
+        Size,               // long
+        Uid,                // int
+        Owner,              // String
+        Gid,                // int
+        Group,              // String
+        IsDirectory,        // boolean
+        IsRegularFile,      // boolean
+        IsSymbolicLink,     // boolean
+        Permissions,        // int
+        CreationTime,       // long
+        LastModifiedTime,   // long
+        LastAccessTime      // long
+    }
+
     /**
      * Get the full path from the base directory of the FileSystemView.
      * @return a path where the path separator is '/' (even if the operating system
@@ -44,6 +61,15 @@ public interface SshFile {
      */
     String getName();
 
+    Map<Attribute,Object> getAttributes() throws IOException;
+
+    void setAttributes(Map<Attribute, Object> attributes) throws IOException;
+
+    Object getAttribute(Attribute attribute) throws IOException;
+
+    void setAttribute(Attribute attribute, Object value) throws IOException;
+
+
     /**
      * Get the owner name of the file
      * @return the name of the owner.

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/21c1cee9/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
index d2796fd..ee7cd6e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
@@ -27,10 +27,22 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.RandomAccessFile;
 import java.lang.reflect.Method;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.StringTokenizer;
 
 import org.apache.sshd.common.file.SshFile;
@@ -591,4 +603,112 @@ public class NativeSshFile implements SshFile {
     public String toString() {
         return fileName;
     }
+
+    public Map<Attribute, Object> getAttributes() throws IOException {
+        Map<String, Object> a = Files.readAttributes(file.toPath(), "unix:size,uid,owner,gid,group,isDirectory,isRegularFile,isSymbolicLink,permissions,creationTime,lastModifiedTime,lastAccessTime", LinkOption.NOFOLLOW_LINKS);
+        Map<Attribute, Object> map = new HashMap<Attribute, Object>();
+        map.put(Attribute.Size, a.get("size"));
+        map.put(Attribute.Uid, a.get("uid"));
+        map.put(Attribute.Owner, ((UserPrincipal) a.get("owner")).getName());
+        map.put(Attribute.Gid, a.get("gid"));
+        map.put(Attribute.Group, ((GroupPrincipal) a.get("group")).getName());
+        map.put(Attribute.IsDirectory, a.get("isDirectory"));
+        map.put(Attribute.IsRegularFile, a.get("isRegularFile"));
+        map.put(Attribute.IsSymbolicLink, a.get("isSymbolicLink"));
+        map.put(Attribute.CreationTime, ((FileTime) a.get("creationTime")).toMillis());
+        map.put(Attribute.LastModifiedTime, ((FileTime) a.get("lastModifiedTime")).toMillis());
+        map.put(Attribute.LastAccessTime, ((FileTime) a.get("lastAccessTime")).toMillis());
+        map.put(Attribute.Permissions, fromPerms((Set<PosixFilePermission>) a.get("permissions")));
+        return map;
+    }
+
+    private int fromPerms(Set<PosixFilePermission> perms) {
+        int p = 0;
+        for (PosixFilePermission perm : perms) {
+            switch (perm) {
+                case OWNER_READ:     p |= 0000400; break;
+                case OWNER_WRITE:    p |= 0000200; break;
+                case OWNER_EXECUTE:  p |= 0000100; break;
+                case GROUP_READ:     p |= 0000040; break;
+                case GROUP_WRITE:    p |= 0000020; break;
+                case GROUP_EXECUTE:  p |= 0000010; break;
+                case OTHERS_READ:    p |= 0000004; break;
+                case OTHERS_WRITE:   p |= 0000002; break;
+                case OTHERS_EXECUTE: p |= 0000001; break;
+            }
+        }
+        return p;
+    }
+
+    public void setAttributes(Map<Attribute, Object> attributes) throws IOException {
+        for (Attribute attribute : attributes.keySet()) {
+            String name = null;
+            Object value = attributes.get(attribute);
+            switch (attribute) {    
+                case Uid:              name = "unix:uid"; break;
+                case Owner:            name = "unix:owner"; value = toUser((String) value); break;
+                case Gid:              name = "unix:gid"; break;
+                case Group:            name = "unix:group"; value = toGroup((String) value); break;
+                case CreationTime:     name = "unix:creationTime"; value = FileTime.fromMillis((Long) value); break;
+                case LastModifiedTime: name = "unix:lastModifiedTime"; value = FileTime.fromMillis((Long) value); break;
+                case LastAccessTime:   name = "unix:lastAccessTime"; value = FileTime.fromMillis((Long) value); break;
+                case Permissions:      name = "unix:permissions"; value = toPerms((Integer) value); break;
+            }
+            if (name != null && value != null) {
+                Files.setAttribute(file.toPath(), name, value, LinkOption.NOFOLLOW_LINKS);
+            }
+        }
+    }
+
+    private GroupPrincipal toGroup(String name) throws IOException {
+        UserPrincipalLookupService lookupService = file.toPath().getFileSystem().getUserPrincipalLookupService();
+        return lookupService.lookupPrincipalByGroupName(name);
+    }
+
+    private UserPrincipal toUser(String name) throws IOException {
+        UserPrincipalLookupService lookupService = file.toPath().getFileSystem().getUserPrincipalLookupService();
+        return lookupService.lookupPrincipalByName(name);
+    }
+
+    private Set<PosixFilePermission> toPerms(int perms) {
+        Set<PosixFilePermission> p = new HashSet<PosixFilePermission>();
+        if ((perms & 0000400) != 0) {
+            p.add(PosixFilePermission.OWNER_READ);
+        }
+        if ((perms & 0000200) != 0) {
+            p.add(PosixFilePermission.OWNER_WRITE);
+        }
+        if ((perms & 0000100) != 0) {
+            p.add(PosixFilePermission.OWNER_EXECUTE);
+        }
+        if ((perms & 0000040) != 0) {
+            p.add(PosixFilePermission.GROUP_READ);
+        }
+        if ((perms & 0000020) != 0) {
+            p.add(PosixFilePermission.GROUP_WRITE);
+        }
+        if ((perms & 0000010) != 0) {
+            p.add(PosixFilePermission.GROUP_EXECUTE);
+        }
+        if ((perms & 0000004) != 0) {
+            p.add(PosixFilePermission.OTHERS_READ);
+        }
+        if ((perms & 0000002) != 0) {
+            p.add(PosixFilePermission.OTHERS_WRITE);
+        }
+        if ((perms & 0000001) != 0) {
+            p.add(PosixFilePermission.OTHERS_EXECUTE);
+        }
+        return p;
+    }
+
+    public Object getAttribute(Attribute attribute) throws IOException {
+        return getAttributes().get(attribute);
+    }
+
+    public void setAttribute(Attribute attribute, Object value) throws IOException {
+        Map<Attribute, Object> map = new HashMap<Attribute, Object>();
+        map.put(attribute, value);
+        setAttributes(map);
+    }
 }