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();