未验证 提交 6f95e400 编写于 作者: J Jesse Glick

Merge branch 'master' into surefire-mixup

......@@ -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 {
* <p>
* please note mask is expected to be an octal if you use <a href="http://en.wikipedia.org/wiki/Chmod">chmod command line values</a>,
* so preceded by a '0' in java notation, ie <code>chmod(0644)</code>
* <p>
* 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<Void>() {
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<Void>() {
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());
......
......@@ -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);
}
......
......@@ -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<PosixFilePermission> 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<PosixFilePermission> 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<PosixFilePermission> 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.
* <p>
* 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");
}
......@@ -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();
}
/**
......
......@@ -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);
}
......@@ -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.
......
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;
}
}
/**
......
......@@ -377,29 +377,11 @@ public abstract class ParameterizedJobMixIn<JobT extends Job<JobT, RunT> & Param
*/
Map<TriggerDescriptor,Trigger<?>> 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);
......
......@@ -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();
......
......@@ -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("---------")));
}
}
......@@ -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();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册