diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/FsPermission.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/FsPermission.java
index 0258293823e..c4e377a9f31 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/FsPermission.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/FsPermission.java
@@ -169,6 +169,18 @@ public class FsPermission implements Writable {
return toShort();
}
+ /**
+ * Returns the FsPermission in an octal format.
+ *
+ * @return short Unlike {@link #toShort()} which provides a binary
+ * representation, this method returns the standard octal style permission.
+ */
+ public short toOctal() {
+ int n = this.toShort();
+ int octal = (n>>>9&1)*1000 + (n>>>6&7)*100 + (n>>>3&7)*10 + (n&7);
+ return (short)octal;
+ }
+
@Override
public boolean equals(Object obj) {
if (obj instanceof FsPermission) {
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Stat.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Stat.java
index 458d3ee7852..42f78437c3b 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Stat.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Stat.java
@@ -31,6 +31,8 @@ import org.apache.hadoop.fs.FileStatus;
/**
* Print statistics about path in specified format.
* Format sequences:
+ * %a: Permissions in octal
+ * %A: Permissions in symbolic style
* %b: Size of file in blocks
* %F: Type
* %g: Group name of owner
@@ -56,7 +58,8 @@ class Stat extends FsCommand {
public static final String USAGE = "[format] ...";
public static final String DESCRIPTION =
"Print statistics about the file/directory at " + NEWLINE +
- "in the specified format. Format accepts filesize in" + NEWLINE +
+ "in the specified format. Format accepts permissions in" + NEWLINE +
+ "octal (%a) and symbolic (%A), filesize in" + NEWLINE +
"blocks (%b), type (%F), group name of owner (%g)," + NEWLINE +
"name (%n), block size (%o), replication (%r), user name" + NEWLINE +
"of owner (%u), modification date (%y, %Y)." + NEWLINE +
@@ -95,6 +98,12 @@ class Stat extends FsCommand {
// this silently drops a trailing %?
if (i + 1 == fmt.length) break;
switch (fmt[++i]) {
+ case 'a':
+ buf.append(stat.getPermission().toOctal());
+ break;
+ case 'A':
+ buf.append(stat.getPermission());
+ break;
case 'b':
buf.append(stat.getLen());
break;
diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/FileSystemShell.md b/hadoop-common-project/hadoop-common/src/site/markdown/FileSystemShell.md
index 1d4174ca8d6..df0bb6c1d67 100644
--- a/hadoop-common-project/hadoop-common/src/site/markdown/FileSystemShell.md
+++ b/hadoop-common-project/hadoop-common/src/site/markdown/FileSystemShell.md
@@ -639,11 +639,11 @@ stat
Usage: `hadoop fs -stat [format] ...`
-Print statistics about the file/directory at \ in the specified format. Format accepts filesize in blocks (%b), type (%F), group name of owner (%g), name (%n), block size (%o), replication (%r), user name of owner(%u), and modification date (%y, %Y). %y shows UTC date as "yyyy-MM-dd HH:mm:ss" and %Y shows milliseconds since January 1, 1970 UTC. If the format is not specified, %y is used by default.
+Print statistics about the file/directory at \ in the specified format. Format accepts permissions in octal (%a) and symbolic (%A), filesize in blocks (%b), type (%F), group name of owner (%g), name (%n), block size (%o), replication (%r), user name of owner(%u), and modification date (%y, %Y). %y shows UTC date as "yyyy-MM-dd HH:mm:ss" and %Y shows milliseconds since January 1, 1970 UTC. If the format is not specified, %y is used by default.
Example:
-* `hadoop fs -stat "%F %u:%g %b %y %n" /file`
+* `hadoop fs -stat "%F %a %u:%g %b %y %n" /file`
Exit Code: Returns 0 on success and -1 on error.
diff --git a/hadoop-common-project/hadoop-common/src/test/resources/testConf.xml b/hadoop-common-project/hadoop-common/src/test/resources/testConf.xml
index 2e13dce7805..be8755aa152 100644
--- a/hadoop-common-project/hadoop-common/src/test/resources/testConf.xml
+++ b/hadoop-common-project/hadoop-common/src/test/resources/testConf.xml
@@ -859,7 +859,11 @@
RegexpComparator
- ^( |\t)*in the specified format. Format accepts filesize in( )*
+ ^( |\t)*in the specified format. Format accepts permissions in( )*
+
+
+ RegexpComparator
+ ^( |\t)*octal \(%a\) and symbolic \(%A\), filesize in( )*
RegexpComparator
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSShell.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSShell.java
index 88f0c959e27..a122972c9a3 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSShell.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSShell.java
@@ -1954,17 +1954,31 @@ public class TestDFSShell {
out.toString(), String.format("%s%n%s%n", mtime1, mtime2));
doFsStat(dfs.getConf(), "%F %u:%g %b %y %n");
-
out.reset();
- doFsStat(dfs.getConf(), "%F %u:%g %b %y %n", testDir1);
+
+ doFsStat(dfs.getConf(), "%F %a %A %u:%g %b %y %n", testDir1);
assertTrue(out.toString(), out.toString().contains(mtime1));
assertTrue(out.toString(), out.toString().contains("directory"));
assertTrue(out.toString(), out.toString().contains(status1.getGroup()));
+ assertTrue(out.toString(),
+ out.toString().contains(status1.getPermission().toString()));
+
+ int n = status1.getPermission().toShort();
+ int octal = (n>>>9&1)*1000 + (n>>>6&7)*100 + (n>>>3&7)*10 + (n&7);
+ assertTrue(out.toString(),
+ out.toString().contains(String.valueOf(octal)));
out.reset();
- doFsStat(dfs.getConf(), "%F %u:%g %b %y %n", testDir1, testFile2);
+ doFsStat(dfs.getConf(), "%F %a %A %u:%g %b %y %n", testDir1, testFile2);
+
+ n = status2.getPermission().toShort();
+ octal = (n>>>9&1)*1000 + (n>>>6&7)*100 + (n>>>3&7)*10 + (n&7);
assertTrue(out.toString(), out.toString().contains(mtime1));
assertTrue(out.toString(), out.toString().contains("regular file"));
+ assertTrue(out.toString(),
+ out.toString().contains(status2.getPermission().toString()));
+ assertTrue(out.toString(),
+ out.toString().contains(String.valueOf(octal)));
assertTrue(out.toString(), out.toString().contains(mtime2));
}