diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index ad13edf5f3..197a27f60b 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -79,6 +79,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.InvalidPathException; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -1582,6 +1583,11 @@ public final class FilePath implements Serializable { *
* please note mask is expected to be an octal if you use chmod command line values,
* so preceded by a '0' in java notation, ie chmod(0644)
+ *
+ * Only supports setting read, write, or execute permissions for the
+ * owner, group, or others, so the largest permissible value is 0777.
+ * Attempting to set larger values (i.e. the setgid, setuid, or sticky
+ * bits) will cause an IOException to be thrown.
*
* @since 1.303
* @see #mode()
@@ -1591,7 +1597,6 @@ public final class FilePath implements Serializable {
act(new SecureFileCallable
+ * This should only be enabled if the setgid/setuid/sticky bits are
+ * intentionally set on the Jenkins installation and they are being
+ * overwritten by Jenkins erroneously.
+ */
+ @Restricted(value = NoExternalUse.class)
+ public static boolean NATIVE_CHMOD_MODE = SystemProperties.getBoolean(Util.class.getName() + ".useNativeChmodAndMode");
}
diff --git a/core/src/main/java/hudson/util/IOUtils.java b/core/src/main/java/hudson/util/IOUtils.java
index d726b43df5..4168379405 100644
--- a/core/src/main/java/hudson/util/IOUtils.java
+++ b/core/src/main/java/hudson/util/IOUtils.java
@@ -1,6 +1,7 @@
package hudson.util;
import hudson.Functions;
+import hudson.Util;
import hudson.os.PosixAPI;
import hudson.os.PosixException;
import java.nio.file.Files;
@@ -119,13 +120,27 @@ public class IOUtils {
/**
- * Gets the mode of a file/directory, if appropriate.
+ * Gets the mode of a file/directory, if appropriate. Only includes read, write, and
+ * execute permissions for the owner, group, and others, i.e. the max return value
+ * is 0777. Consider using {@link Files#getPosixFilePermissions} instead if you only
+ * care about access permissions.
+ *
* @return a file mode, or -1 if not on Unix
* @throws PosixException if the file could not be statted, e.g. broken symlink
*/
public static int mode(File f) throws PosixException {
if(Functions.isWindows()) return -1;
- return PosixAPI.jnr().stat(f.getPath()).mode();
+ try {
+ if (Util.NATIVE_CHMOD_MODE) {
+ return PosixAPI.jnr().stat(f.getPath()).mode();
+ } else {
+ return Util.permissionsToMode(Files.getPosixFilePermissions(Util.fileToPath(f)));
+ }
+ } catch (IOException cause) {
+ PosixException e = new PosixException("Unable to get file permissions", null);
+ e.initCause(cause);
+ throw e;
+ }
}
/**
diff --git a/core/src/test/java/hudson/FilePathTest.java b/core/src/test/java/hudson/FilePathTest.java
index 84ca31d889..846900424e 100644
--- a/core/src/test/java/hudson/FilePathTest.java
+++ b/core/src/test/java/hudson/FilePathTest.java
@@ -25,6 +25,7 @@ package hudson;
import hudson.FilePath.TarCompression;
import hudson.model.TaskListener;
+import hudson.os.PosixAPI;
import hudson.remoting.VirtualChannel;
import hudson.util.NullStream;
import hudson.util.StreamTaskListener;
@@ -245,7 +246,7 @@ public class FilePathTest {
throw x;
}
} finally {
- toF.chmod(700);
+ toF.chmod(0700);
}
}
@@ -471,6 +472,25 @@ public class FilePathTest {
}
}
+ @Test public void copyToWithPermissionSpecialPermissions() throws IOException, InterruptedException {
+ assumeFalse("Test uses POSIX-specific features", Functions.isWindows());
+ File tmp = temp.getRoot();
+ File original = new File(tmp,"original");
+ FilePath originalP = new FilePath(channels.french, original.getPath());
+ originalP.touch(0);
+ PosixAPI.jnr().chmod(original.getAbsolutePath(), 02777); // Read/write/execute for everyone and setuid.
+
+ File sameChannelCopy = new File(tmp,"sameChannelCopy");
+ FilePath sameChannelCopyP = new FilePath(channels.french, sameChannelCopy.getPath());
+ originalP.copyToWithPermission(sameChannelCopyP);
+ assertEquals("Special permissions should be copied on the same machine", 02777, PosixAPI.jnr().stat(sameChannelCopy.getAbsolutePath()).mode() & 07777);
+
+ File diffChannelCopy = new File(tmp,"diffChannelCopy");
+ FilePath diffChannelCopyP = new FilePath(channels.british, diffChannelCopy.getPath());
+ originalP.copyToWithPermission(diffChannelCopyP);
+ assertEquals("Special permissions should not be copied across machines", 00777, PosixAPI.jnr().stat(diffChannelCopy.getAbsolutePath()).mode() & 07777);
+ }
+
@Test public void symlinkInTar() throws Exception {
assumeFalse("can't test on Windows", Functions.isWindows());
@@ -739,7 +759,38 @@ public class FilePathTest {
// and now fail when flush is bad!
tmpDirPath.child("../" + archive.getName()).untar(outDir, TarCompression.NONE);
}
+
+ @Test
+ public void chmod() throws Exception {
+ assumeFalse(Functions.isWindows());
+ File f = temp.newFile("file");
+ FilePath fp = new FilePath(f);
+ int prevMode = fp.mode();
+ assertEquals(0400, chmodAndMode(fp, 0400));
+ assertEquals(0412, chmodAndMode(fp, 0412));
+ assertEquals(0777, chmodAndMode(fp, 0777));
+ assertEquals(prevMode, chmodAndMode(fp, prevMode));
+ }
+ @Test
+ public void chmodInvalidPermissions() throws Exception {
+ assumeFalse(Functions.isWindows());
+ File f = temp.newFolder("folder");
+ FilePath fp = new FilePath(f);
+ int invalidMode = 01770; // Full permissions for owner and group plus sticky bit.
+ try {
+ chmodAndMode(fp, invalidMode);
+ fail("Setting sticky bit should fail");
+ } catch (IOException e) {
+ assertEquals("Invalid mode: " + invalidMode, e.getMessage());
+ }
+ }
+
+ private int chmodAndMode(FilePath path, int mode) throws Exception {
+ path.chmod(mode);
+ return path.mode();
+ }
+
@Test public void deleteRecursiveOnUnix() throws Exception {
assumeFalse("Uses Unix-specific features", Functions.isWindows());
Path targetDir = temp.newFolder("target").toPath();
diff --git a/core/src/test/java/hudson/UtilTest.java b/core/src/test/java/hudson/UtilTest.java
index 3cef81aae9..21a8993de4 100644
--- a/core/src/test/java/hudson/UtilTest.java
+++ b/core/src/test/java/hudson/UtilTest.java
@@ -36,6 +36,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.attribute.PosixFilePermissions;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
@@ -43,6 +44,7 @@ import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.*;
import org.apache.commons.io.FileUtils;
@@ -724,4 +726,41 @@ public class UtilTest {
assertTrue(Util.isDescendant(new File(root, "."), new File(new File(root, "child"), ".")));
}
+ @Test
+ public void testModeToPermissions() throws Exception {
+ assertEquals(PosixFilePermissions.fromString("rwxrwxrwx"), Util.modeToPermissions(0777));
+ assertEquals(PosixFilePermissions.fromString("rwxr-xrwx"), Util.modeToPermissions(0757));
+ assertEquals(PosixFilePermissions.fromString("rwxr-x---"), Util.modeToPermissions(0750));
+ assertEquals(PosixFilePermissions.fromString("r-xr-x---"), Util.modeToPermissions(0550));
+ assertEquals(PosixFilePermissions.fromString("r-xr-----"), Util.modeToPermissions(0540));
+ assertEquals(PosixFilePermissions.fromString("--xr-----"), Util.modeToPermissions(0140));
+ assertEquals(PosixFilePermissions.fromString("--xr---w-"), Util.modeToPermissions(0142));
+ assertEquals(PosixFilePermissions.fromString("--xr--rw-"), Util.modeToPermissions(0146));
+ assertEquals(PosixFilePermissions.fromString("-wxr--rw-"), Util.modeToPermissions(0346));
+ assertEquals(PosixFilePermissions.fromString("---------"), Util.modeToPermissions(0000));
+
+ assertEquals("Non-permission bits should be ignored", PosixFilePermissions.fromString("r-xr-----"), Util.modeToPermissions(0100540));
+
+ try {
+ Util.modeToPermissions(01777);
+ fail("Did not detect invalid mode");
+ } catch (IOException e) {
+ assertThat(e.getMessage(), startsWith("Invalid mode"));
+ }
+ }
+
+ @Test
+ public void testPermissionsToMode() throws Exception {
+ assertEquals(0777, Util.permissionsToMode(PosixFilePermissions.fromString("rwxrwxrwx")));
+ assertEquals(0757, Util.permissionsToMode(PosixFilePermissions.fromString("rwxr-xrwx")));
+ assertEquals(0750, Util.permissionsToMode(PosixFilePermissions.fromString("rwxr-x---")));
+ assertEquals(0550, Util.permissionsToMode(PosixFilePermissions.fromString("r-xr-x---")));
+ assertEquals(0540, Util.permissionsToMode(PosixFilePermissions.fromString("r-xr-----")));
+ assertEquals(0140, Util.permissionsToMode(PosixFilePermissions.fromString("--xr-----")));
+ assertEquals(0142, Util.permissionsToMode(PosixFilePermissions.fromString("--xr---w-")));
+ assertEquals(0146, Util.permissionsToMode(PosixFilePermissions.fromString("--xr--rw-")));
+ assertEquals(0346, Util.permissionsToMode(PosixFilePermissions.fromString("-wxr--rw-")));
+ assertEquals(0000, Util.permissionsToMode(PosixFilePermissions.fromString("---------")));
+ }
+
}
diff --git a/core/src/test/java/hudson/util/io/TarArchiverTest.java b/core/src/test/java/hudson/util/io/TarArchiverTest.java
index 144be06c55..7635ccc97b 100644
--- a/core/src/test/java/hudson/util/io/TarArchiverTest.java
+++ b/core/src/test/java/hudson/util/io/TarArchiverTest.java
@@ -83,9 +83,9 @@ public class TarArchiverTest {
// extract via the tar command
run(e, "tar", "xvpf", tar.getAbsolutePath());
- assertEquals(0100755,e.child("a.txt").mode());
+ assertEquals(0755,e.child("a.txt").mode());
assertEquals(dirMode,e.child("subdir").mode());
- assertEquals(0100644,e.child("subdir/b.txt").mode());
+ assertEquals(0644,e.child("subdir/b.txt").mode());
// extract via the zip command
@@ -93,9 +93,9 @@ public class TarArchiverTest {
run(e, "unzip", zip.getAbsolutePath());
e = e.listDirectories().get(0);
- assertEquals(0100755, e.child("a.txt").mode());
+ assertEquals(0755, e.child("a.txt").mode());
assertEquals(dirMode,e.child("subdir").mode());
- assertEquals(0100644,e.child("subdir/b.txt").mode());
+ assertEquals(0644,e.child("subdir/b.txt").mode());
} finally {
tar.delete();
zip.delete();