diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index ad13edf5f338a1c5a1c0a81b9f3c0bae6d7cdbc1..197a27f60b6360e8404ce40303411c42bb097989 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() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
- // TODO first check for Java 7+ and use PosixFileAttributeView
_chmod(writing(f), mask);
return null;
@@ -1600,14 +1605,18 @@ public final class FilePath implements Serializable {
}
/**
- * Run chmod via jnr-posix
+ * Change permissions via NIO.
*/
private static void _chmod(File f, int mask) throws IOException {
// TODO WindowsPosix actually does something here (WindowsLibC._wchmod); should we let it?
// Anyway the existing calls already skip this method if on Windows.
if (File.pathSeparatorChar==';') return; // noop
- PosixAPI.jnr().chmod(f.getAbsolutePath(),mask);
+ if (Util.NATIVE_CHMOD_MODE) {
+ PosixAPI.jnr().chmod(f.getAbsolutePath(), mask);
+ } else {
+ Files.setPosixFilePermissions(Util.fileToPath(f), Util.modeToPermissions(mask));
+ }
}
private static boolean CHMOD_WARNED = false;
@@ -2008,6 +2017,21 @@ public final class FilePath implements Serializable {
* @since 1.311
*/
public void copyToWithPermission(FilePath target) throws IOException, InterruptedException {
+ // Use NIO copy with StandardCopyOption.COPY_ATTRIBUTES when copying on the same machine.
+ if (this.channel == target.channel) {
+ act(new SecureFileCallable() {
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ File targetFile = new File(target.remote);
+ File targetDir = targetFile.getParentFile();
+ filterNonNull().mkdirs(targetDir);
+ Files.createDirectories(Util.fileToPath(targetDir));
+ Files.copy(Util.fileToPath(reading(f)), Util.fileToPath(writing(targetFile)), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
+ return null;
+ }
+ });
+ return;
+ }
+
copyTo(target);
// copy file permission
target.chmod(mode());
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index 9203efd03ca049950f4fe6981ba14f171b1350a0..c9c5e8545be45d1e09eb5eec9e55aa3e8ed510e5 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -125,6 +125,7 @@ import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.regex.Pattern;
+import javax.annotation.Nullable;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
@@ -1142,13 +1143,17 @@ public class Functions {
* Gets the relative name or display name to the given item from the specified group.
*
* @since 1.515
- * @param p the Item we want the relative display name
- * @param g the ItemGroup used as point of reference for the item
+ * @param p the Item we want the relative display name.
+ * If {@code null}, a {@code null} will be returned by the method
+ * @param g the ItemGroup used as point of reference for the item.
+ * If the group is not specified, item's path will be used.
* @param useDisplayName if true, returns a display name, otherwise returns a name
* @return
- * String like "foo » bar"
+ * String like "foo » bar".
+ * {@code null} if item is null or if one of its parents is not an {@link Item}.
*/
- public static String getRelativeNameFrom(Item p, ItemGroup g, boolean useDisplayName) {
+ @Nullable
+ public static String getRelativeNameFrom(@CheckForNull Item p, @CheckForNull ItemGroup g, boolean useDisplayName) {
if (p == null) return null;
if (g == null) return useDisplayName ? p.getFullDisplayName() : p.getFullName();
String separationString = useDisplayName ? " » " : "/";
@@ -1182,7 +1187,7 @@ public class Functions {
if (gr instanceof Item)
i = (Item)gr;
- else
+ else // Parent is a group, but not an item
return null;
}
}
@@ -1192,11 +1197,14 @@ public class Functions {
*
* @since 1.515
* @param p the Item we want the relative display name
+ * If {@code null}, the method will immediately return {@code null}.
* @param g the ItemGroup used as point of reference for the item
* @return
- * String like "foo/bar"
+ * String like "foo/bar".
+ * {@code null} if the item is {@code null} or if one of its parents is not an {@link Item}.
*/
- public static String getRelativeNameFrom(Item p, ItemGroup g) {
+ @Nullable
+ public static String getRelativeNameFrom(@CheckForNull Item p, @CheckForNull ItemGroup g) {
return getRelativeNameFrom(p, g, false);
}
@@ -1205,12 +1213,15 @@ public class Functions {
* Gets the relative display name to the given item from the specified group.
*
* @since 1.512
- * @param p the Item we want the relative display name
+ * @param p the Item we want the relative display name.
+ * If {@code null}, the method will immediately return {@code null}.
* @param g the ItemGroup used as point of reference for the item
* @return
- * String like "Foo » Bar"
+ * String like "Foo » Bar".
+ * {@code null} if the item is {@code null} or if one of its parents is not an {@link Item}.
*/
- public static String getRelativeDisplayNameFrom(Item p, ItemGroup g) {
+ @Nullable
+ public static String getRelativeDisplayNameFrom(@CheckForNull Item p, @CheckForNull ItemGroup g) {
return getRelativeNameFrom(p, g, true);
}
diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java
index fc0afb15051bc998079b776c903dbefbd5fefda8..8ee5c7390a3b8e5d93d1fd8f81cc1283cb2c06ca 100644
--- a/core/src/main/java/hudson/Util.java
+++ b/core/src/main/java/hudson/Util.java
@@ -68,6 +68,7 @@ import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributes;
import java.security.MessageDigest;
@@ -1597,6 +1598,51 @@ public class Util {
}
}
+ @Restricted(NoExternalUse.class)
+ public static int permissionsToMode(Set permissions) {
+ PosixFilePermission[] allPermissions = PosixFilePermission.values();
+ int result = 0;
+ for (int i = 0; i < allPermissions.length; i++) {
+ result <<= 1;
+ result |= permissions.contains(allPermissions[i]) ? 1 : 0;
+ }
+ return result;
+ }
+
+ @Restricted(NoExternalUse.class)
+ public static Set modeToPermissions(int mode) throws IOException {
+ // Anything larger is a file type, not a permission.
+ int PERMISSIONS_MASK = 07777;
+ // setgid/setuid/sticky are not supported.
+ int MAX_SUPPORTED_MODE = 0777;
+ mode = mode & PERMISSIONS_MASK;
+ if ((mode & MAX_SUPPORTED_MODE) != mode) {
+ throw new IOException("Invalid mode: " + mode);
+ }
+ PosixFilePermission[] allPermissions = PosixFilePermission.values();
+ Set result = EnumSet.noneOf(PosixFilePermission.class);
+ for (int i = 0; i < allPermissions.length; i++) {
+ if ((mode & 1) == 1) {
+ result.add(allPermissions[allPermissions.length - i - 1]);
+ }
+ mode >>= 1;
+ }
+ return result;
+ }
+
+ /**
+ * Converts a {@link File} into a {@link Path} and checks runtime exceptions.
+ * @throws IOException if {@code f.toPath()} throws {@link InvalidPathException}.
+ */
+ @Restricted(NoExternalUse.class)
+ public static @Nonnull Path fileToPath(@Nonnull File file) throws IOException {
+ try {
+ return file.toPath();
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
+ }
+ }
+
public static final FastDateFormat XS_DATETIME_FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",new SimpleTimeZone(0,"GMT"));
// Note: RFC822 dates must not be localized!
@@ -1664,4 +1710,15 @@ public class Util {
*/
@Restricted(value = NoExternalUse.class)
static boolean GC_AFTER_FAILED_DELETE = SystemProperties.getBoolean(Util.class.getName() + ".performGCOnFailedDelete");
+
+ /**
+ * If this flag is true, native implementations of {@link FilePath#chmod}
+ * and {@link hudson.util.IOUtils#mode} are used instead of NIO.
+ *
+ * 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/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java
index 01ef3a0bbf2fff28ccccd31e51e600c24d0191a6..4c026d5f312e471a2e79ec83934085b57bc975a6 100644
--- a/core/src/main/java/hudson/model/AbstractItem.java
+++ b/core/src/main/java/hudson/model/AbstractItem.java
@@ -381,21 +381,6 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
return getRelativeNameFrom(p);
}
- /**
- * @param p
- * The ItemGroup instance used as context to evaluate the relative name of this AbstractItem
- * @return
- * The name of the current item, relative to p.
- * Nested ItemGroups are separated by / character.
- */
- public String getRelativeNameFrom(ItemGroup p) {
- return Functions.getRelativeNameFrom(this, p);
- }
-
- public String getRelativeNameFrom(Item item) {
- return getRelativeNameFrom(item.getParent());
- }
-
/**
* Called right after when a {@link Item} is loaded from disk.
* This is an opportunity to do a post load processing.
@@ -470,12 +455,10 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
return getShortUrl();
}
+ @Override
@Exported(visibility=999,name="url")
public final String getAbsoluteUrl() {
- String r = Jenkins.getInstance().getRootUrl();
- if(r==null)
- throw new IllegalStateException("Root URL isn't configured yet. Cannot compute absolute URL.");
- return Util.encode(r+getUrl());
+ return Item.super.getAbsoluteUrl();
}
/**
diff --git a/core/src/main/java/hudson/model/BuildableItem.java b/core/src/main/java/hudson/model/BuildableItem.java
index ef845e11750de8a31ae6ff4c2627ddc6594ceb3f..0d65214e4378535633be61f177c4b2174ad78b52 100644
--- a/core/src/main/java/hudson/model/BuildableItem.java
+++ b/core/src/main/java/hudson/model/BuildableItem.java
@@ -40,13 +40,19 @@ public interface BuildableItem extends Item, Task {
* Use {@link #scheduleBuild(Cause)}. Since 1.283
*/
@Deprecated
- boolean scheduleBuild();
+ default boolean scheduleBuild() {
+ return scheduleBuild(new Cause.LegacyCodeCause());
+ }
+
boolean scheduleBuild(Cause c);
/**
* @deprecated
* Use {@link #scheduleBuild(int, Cause)}. Since 1.283
*/
@Deprecated
- boolean scheduleBuild(int quietPeriod);
+ default boolean scheduleBuild(int quietPeriod) {
+ return scheduleBuild(quietPeriod, new Cause.LegacyCodeCause());
+ }
+
boolean scheduleBuild(int quietPeriod, Cause c);
}
diff --git a/core/src/main/java/hudson/model/Item.java b/core/src/main/java/hudson/model/Item.java
index d0c438744d86899d6b7c28b8124f1778c400d9c5..8187d608bd8923851cf945f0c6db09971b7836d1 100644
--- a/core/src/main/java/hudson/model/Item.java
+++ b/core/src/main/java/hudson/model/Item.java
@@ -25,9 +25,12 @@
package hudson.model;
import hudson.Functions;
+import hudson.Util;
+import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
import hudson.security.PermissionScope;
import jenkins.util.io.OnMaster;
+import jline.internal.Nullable;
import org.kohsuke.stapler.StaplerRequest;
import java.io.IOException;
@@ -39,6 +42,9 @@ import hudson.security.PermissionGroup;
import hudson.security.AccessControlled;
import hudson.util.Secret;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
/**
* Basic configuration unit in Hudson.
*
@@ -131,18 +137,32 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont
/**
* Gets the relative name to this item from the specified group.
*
+ * @param g
+ * The {@link ItemGroup} instance used as context to evaluate the relative name of this item
+ * @return
+ * The name of the current item, relative to p. Nested {@link ItemGroup}s are separated by {@code /} character.
* @since 1.419
* @return
- * String like "../foo/bar"
+ * String like "../foo/bar".
+ * {@code null} if one of item parents is not an {@link Item}.
*/
- String getRelativeNameFrom(ItemGroup g);
+ @Nullable
+ default String getRelativeNameFrom(@CheckForNull ItemGroup g) {
+ return Functions.getRelativeNameFrom(this, g);
+ }
/**
* Short for {@code getRelativeNameFrom(item.getParent())}
*
+ * @return String like "../foo/bar".
+ * {@code null} if one of item parents is not an {@link Item}.
* @since 1.419
*/
- String getRelativeNameFrom(Item item);
+ @Nullable
+ default String getRelativeNameFrom(@Nonnull Item item) {
+ return getRelativeNameFrom(item.getParent());
+
+ }
/**
* Returns the URL of this item relative to the context root of the application.
@@ -180,7 +200,12 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont
* (even this won't work for the same reason, which should be fixed.)
*/
@Deprecated
- String getAbsoluteUrl();
+ default String getAbsoluteUrl() {
+ String r = Jenkins.getInstance().getRootUrl();
+ if(r==null)
+ throw new IllegalStateException("Root URL isn't configured yet. Cannot compute absolute URL.");
+ return Util.encode(r+getUrl());
+ }
/**
* Called right after when a {@link Item} is loaded from disk.
@@ -207,7 +232,9 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont
*
* @since 1.374
*/
- default void onCreatedFromScratch() {}
+ default void onCreatedFromScratch() {
+ // do nothing by default
+ }
/**
* Save the settings to a file.
diff --git a/core/src/main/java/hudson/util/IOUtils.java b/core/src/main/java/hudson/util/IOUtils.java
index d726b43df50954c4ebd72c5579335bfc9531fe4e..41683794054c72fe2e4dcd14c82382a14ae9c231 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/main/java/jenkins/model/ParameterizedJobMixIn.java b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
index 7cfbc23baf94d7f3de7103d07fe1b9978f54392d..55301e4f8636ae60f62433fc8059daf5be053a41 100644
--- a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
+++ b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
@@ -377,29 +377,11 @@ public abstract class ParameterizedJobMixIn & Param
*/
Map> getTriggers();
- /**
- * @deprecated use {@link #scheduleBuild(Cause)}
- */
- @Deprecated
- @Override
- default boolean scheduleBuild() {
- return getParameterizedJobMixIn().scheduleBuild();
- }
-
@Override
default boolean scheduleBuild(Cause c) {
return getParameterizedJobMixIn().scheduleBuild(c);
}
- /**
- * @deprecated use {@link #scheduleBuild(int, Cause)}
- */
- @Deprecated
- @Override
- default boolean scheduleBuild(int quietPeriod) {
- return getParameterizedJobMixIn().scheduleBuild(quietPeriod);
- }
-
@Override
default boolean scheduleBuild(int quietPeriod, Cause c) {
return getParameterizedJobMixIn().scheduleBuild(quietPeriod, c);
diff --git a/core/src/test/java/hudson/FilePathTest.java b/core/src/test/java/hudson/FilePathTest.java
index 84ca31d88961a25d3172696467b7ef7cd9b0ebd0..846900424e63bc6fe70ff9aa44211c0700fd4495 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 3cef81aae9640dd3c4eba746b6f562f60514e87a..21a8993de46628b7d1b072b02742f6de4beff979 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 144be06c55a1ac01f7d0babf16e2ebf5ecc5822f..7635ccc97b1f07a2c77cf312dd095e62c30a082d 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();