Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3f01a77
[JENKINS-49635] Defining new VirtualFile methods to better support ex…
jglick Feb 19, 2018
8e603a5
[JENKINS-26810] Added VirtualFile.mode.
jglick Feb 21, 2018
e773bdb
New VirtualFile.list overload with more capabilities and more precise…
jglick Feb 21, 2018
4911c5c
More compatible default implementation of VirtualFile.list(String, St…
jglick Feb 21, 2018
e3e6d57
VirtualFile.readLink
jglick Feb 21, 2018
2e070ca
Forgotten since tags.
jglick Feb 21, 2018
9735a34
Compilation error.
jglick Feb 21, 2018
a9430d3
Mistake caught by DirectoryBrowserSupportTest.zipDownload.
jglick Feb 21, 2018
e0fab1a
list(String, String, boolean) was returning \-separated paths on Wind…
jglick Feb 21, 2018
9ccffba
SelectorUtils.tokenizePath apparently expects the native path separat…
jglick Feb 21, 2018
7131067
Minor comments and simplifications.
jglick Feb 21, 2018
9b9f81d
Merge branch 'master' into VirtualFile-JENKINS-49635
jglick Feb 28, 2018
23f12fd
Merge branch 'master' into VirtualFile-JENKINS-49635
jglick Mar 5, 2018
63885e6
Typo noticed by @olivergondza.
jglick Mar 5, 2018
64ee6fd
Merge branch 'master' into VirtualFile-JENKINS-49635
jglick Mar 6, 2018
c7372cc
Merge branch 'master' into VirtualFile-JENKINS-49635
jglick Mar 12, 2018
fca93db
Merge branch 'master' into VirtualFile-JENKINS-49635
jglick Mar 13, 2018
d32b9b1
@oleg-nenashev suggests documenting behavior of VirtualFile.mode on s…
jglick Mar 14, 2018
beea2aa
More details on toURI and toExternalURL.
jglick Mar 14, 2018
7223632
Should not attempt to call VirtualFile.child with the empty string.
jglick Mar 16, 2018
d3f5346
Merge branch 'SystemProperties-JENKINS-46386' into VirtualFile-JENKIN…
jglick Mar 21, 2018
2e1af78
Merge branch 'master' into VirtualFile-JENKINS-49635
jglick Mar 23, 2018
90fd89f
Use run(Callable) from the default list(String, String, boolean) so i…
jglick Mar 23, 2018
9b75cd2
Making Run.getArtifactsUpTo call VirtualFile.run.
jglick Mar 23, 2018
8e60538
Merge branch 'master' into VirtualFile-JENKINS-49635
jglick Mar 26, 2018
2090468
Using new beta annotation.
jglick Mar 26, 2018
a345051
Merge branch 'master' into VirtualFile-JENKINS-49635
jglick Apr 2, 2018
547fd6f
Removing VirtualFile.asRemotable in favor of toExternalURL.
jglick Apr 2, 2018
a54d49b
access-modifier.version=1.14
jglick Apr 3, 2018
d25c0b2
Merge branch 'master' into VirtualFile-JENKINS-49635
jglick Apr 13, 2018
8f5d1d3
Merge branch 'master' into VirtualFile-JENKINS-49635
jglick Apr 16, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions core/src/main/java/hudson/FilePath.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@

import java.util.Collections;
import org.apache.tools.ant.BuildException;
import org.kohsuke.accmod.restrictions.Beta;

/**
* {@link File} like object with remoting support.
Expand Down Expand Up @@ -903,6 +904,28 @@ public void copyFrom(URL url) throws IOException, InterruptedException {
}
}

/**
* Copies the content of a URL to a remote file.
* Unlike {@link #copyFrom} this will not transfer content over a Remoting channel.
* @since FIXME
*/
@Restricted(Beta.class)
public void copyFromRemotely(URL url) throws IOException, InterruptedException {
act(new CopyFromRemotely(url));
}
private final class CopyFromRemotely extends MasterToSlaveFileCallable<Void> {
private static final long serialVersionUID = 1;
private final URL url;
CopyFromRemotely(URL url) {
this.url = url;
}
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
copyFrom(url);
return null;
}
}

/**
* Replaces the content of this file by the data from the given {@link InputStream}.
*
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/hudson/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -1420,7 +1420,7 @@ public static File resolveSymlinkToFile(@Nonnull File link) throws InterruptedEx
* The relative path is meant to be resolved from the location of the symlink.
*/
@CheckForNull
public static String resolveSymlink(@Nonnull File link) throws InterruptedException, IOException {
public static String resolveSymlink(@Nonnull File link) throws IOException {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binary compatible simplification—the body never actually threw that exception.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not source compatible IIRC (javac fails when there is a catch block for impossible exception), may impact PCT. There are usages of it externally, e.g. in the Support Core plugin: https://github.com/search?p=1&q=org%3Ajenkinsci+resolveSymlink&type=Code

I would propose to detach it to a separate PR if you feel it's important enough

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oleg-nenashev Jesse already disputed this in #3340 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, PCT should be unaffected, though it could impede

buildPlugin(jenkinsVersions: [null, '2.222']) // or whatever

Why the Java language treats an exception which cannot be thrown as a fatal error rather than a warning (ditto unreachable statements etc.), I do not know.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simple workaround for the buildPlugin case would be to just catch (Exception I guess.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be better to fix it tho

try {
Path path = fileToPath(link);
return Files.readSymbolicLink(path).toString();
Expand Down
22 changes: 17 additions & 5 deletions core/src/main/java/hudson/model/DirectoryBrowserSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URL;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
Expand All @@ -51,6 +53,7 @@
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

Expand Down Expand Up @@ -213,7 +216,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
String rest = _rest.toString();

// this is the base file/directory
VirtualFile baseFile = root.child(base);
VirtualFile baseFile = base.isEmpty() ? root : root.child(base);

if(baseFile.isDirectory()) {
if(zip) {
Expand Down Expand Up @@ -295,6 +298,14 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
return;
}

URL external = baseFile.toExternalURL();
if (external != null) {
// or this URL could be emitted directly from dir.jelly
// though we would prefer to delay toExternalURL calls unless and until needed
rsp.sendRedirect2(external.toExternalForm());
return;
}

long lastModified = baseFile.lastModified();
long length = baseFile.length();

Expand Down Expand Up @@ -355,7 +366,8 @@ private static String createBackRef(int times) {
private static void zip(OutputStream outputStream, VirtualFile dir, String glob) throws IOException {
try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
zos.setEncoding(System.getProperty("file.encoding")); // TODO JENKINS-20663 make this overridable via query parameter
for (String n : dir.list(glob.length() == 0 ? "**" : glob)) {
// TODO consider using run(Callable) here
for (String n : dir.list(glob.isEmpty() ? "**" : glob, null, /* TODO what is the user expectation? */true)) {
String relativePath;
if (glob.length() == 0) {
// JENKINS-19947: traditional behavior is to prepend the directory name
Expand Down Expand Up @@ -535,10 +547,10 @@ private static List<List<Path>> buildChildPaths(VirtualFile cur, Locale locale)
* @param baseRef String like "../../../" that cancels the 'rest' portion. Can be "./"
*/
private static List<List<Path>> patternScan(VirtualFile baseDir, String pattern, String baseRef) throws IOException {
String[] files = baseDir.list(pattern);
Collection<String> files = baseDir.list(pattern, null, /* TODO what is the user expectation? */true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that there was a JEP-200 regression in the similar code recently

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, in DirScanner.Glob IIRC, which is called by some list implementations.


if (files.length > 0) {
List<List<Path>> r = new ArrayList<List<Path>>(files.length);
if (!files.isEmpty()) {
List<List<Path>> r = new ArrayList<List<Path>>(files.size());
for (String match : files) {
List<Path> file = buildPathList(baseDir, baseDir.child(match), baseRef);
r.add(file);
Expand Down
89 changes: 78 additions & 11 deletions core/src/main/java/hudson/model/Run.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
Expand Down Expand Up @@ -108,6 +109,7 @@
import jenkins.model.StandardArtifactManager;
import jenkins.model.lazy.BuildReference;
import jenkins.model.lazy.LazyBuildMixIn;
import jenkins.security.MasterToSlaveCallable;
import jenkins.util.VirtualFile;
import jenkins.util.io.OnMaster;
import net.sf.json.JSONObject;
Expand Down Expand Up @@ -1090,12 +1092,16 @@ public File getArtifactsDir() {
* @return The list can be empty but never null
*/
public @Nonnull List<Artifact> getArtifactsUpTo(int artifactsNumber) {
ArtifactList r = new ArtifactList();
SerializableArtifactList sal;
VirtualFile root = getArtifactManager().root();
try {
addArtifacts(getArtifactManager().root(), "", "", r, null, artifactsNumber);
sal = root.run(new AddArtifacts(root, artifactsNumber));
} catch (IOException x) {
LOGGER.log(Level.WARNING, null, x);
sal = new SerializableArtifactList();
}
ArtifactList r = new ArtifactList();
r.updateFrom(sal);
r.computeDisplayName();
return r;
}
Expand All @@ -1109,9 +1115,25 @@ public boolean getHasArtifacts() {
return !getArtifactsUpTo(1).isEmpty();
}

private int addArtifacts(@Nonnull VirtualFile dir,
private static final class AddArtifacts extends MasterToSlaveCallable<SerializableArtifactList, IOException> {
private static final long serialVersionUID = 1L;
private final VirtualFile root;
private final int artifactsNumber;
AddArtifacts(VirtualFile root, int artifactsNumber) {
this.root = root;
this.artifactsNumber = artifactsNumber;
}
@Override
public SerializableArtifactList call() throws IOException {
SerializableArtifactList sal = new SerializableArtifactList();
addArtifacts(root, "", "", sal, null, artifactsNumber);
return sal;
}
}

private static int addArtifacts(@Nonnull VirtualFile dir,
@Nonnull String path, @Nonnull String pathHref,
@Nonnull ArtifactList r, @Nonnull Artifact parent, int upTo) throws IOException {
@Nonnull SerializableArtifactList r, @Nonnull SerializableArtifact parent, int upTo) throws IOException {
VirtualFile[] kids = dir.list();
Arrays.sort(kids);

Expand All @@ -1122,26 +1144,26 @@ private int addArtifacts(@Nonnull VirtualFile dir,
String childHref = pathHref + Util.rawEncode(child);
String length = sub.isFile() ? String.valueOf(sub.length()) : "";
boolean collapsed = (kids.length==1 && parent!=null);
Artifact a;
SerializableArtifact a;
if (collapsed) {
// Collapse single items into parent node where possible:
a = new Artifact(parent.getFileName() + '/' + child, childPath,
a = new SerializableArtifact(parent.name + '/' + child, childPath,
sub.isDirectory() ? null : childHref, length,
parent.getTreeNodeId());
parent.treeNodeId);
r.tree.put(a, r.tree.remove(parent));
} else {
// Use null href for a directory:
a = new Artifact(child, childPath,
a = new SerializableArtifact(child, childPath,
sub.isDirectory() ? null : childHref, length,
"n" + ++r.idSeq);
r.tree.put(a, parent!=null ? parent.getTreeNodeId() : null);
r.tree.put(a, parent!=null ? parent.treeNodeId : null);
}
if (sub.isDirectory()) {
n += addArtifacts(sub, childPath + '/', childHref + '/', r, a, upTo-n);
if (n>=upTo) break;
} else {
// Don't store collapsed path in ArrayList (for correct data in external API)
r.add(collapsed ? new Artifact(child, a.relativePath, a.href, length, a.treeNodeId) : a);
r.add(collapsed ? new SerializableArtifact(child, a.relativePath, a.href, length, a.treeNodeId) : a);
if (++n>=upTo) break;
}
}
Expand All @@ -1159,6 +1181,30 @@ private int addArtifacts(@Nonnull VirtualFile dir,
public static final int TREE_CUTOFF = Integer.parseInt(SystemProperties.getString("hudson.model.Run.ArtifactList.treeCutoff", "40"));

// ..and then "too many"

/** {@link Run.Artifact} without the implicit link to {@link Run} */
private static final class SerializableArtifact implements Serializable {
private static final long serialVersionUID = 1L;
final String name;
final String relativePath;
final String href;
final String length;
final String treeNodeId;
SerializableArtifact(String name, String relativePath, String href, String length, String treeNodeId) {
this.name = name;
this.relativePath = relativePath;
this.href = href;
this.length = length;
this.treeNodeId = treeNodeId;
}
}

/** {@link Run.ArtifactList} without the implicit link to {@link Run} */
private static final class SerializableArtifactList extends ArrayList<SerializableArtifact> {
private static final long serialVersionUID = 1L;
private LinkedHashMap<SerializableArtifact, String> tree = new LinkedHashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even for a private class not looks a bit dangerous since you do not override modification methods to put data to both containers (array and tree)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was already a mess and I am trying to touch it as little as possible. The problem here was that Artifact and ArtifactList were, surprisingly, nonstatic—they held a Run reference—which is unsupportable when they must be constructed inside a callable which might run remotely. (If using the FilePath-based VirtualFile, for example. In fact this is not currently used by any ArtifactManager, only workspace browsing, but the API does not preclude the possibility, so…)

private int idSeq = 0;
}

public final class ArtifactList extends ArrayList<Artifact> {
private static final long serialVersionUID = 1L;
Expand All @@ -1167,7 +1213,24 @@ public final class ArtifactList extends ArrayList<Artifact> {
* Contains Artifact objects for directories and files (the ArrayList contains only files).
*/
private LinkedHashMap<Artifact,String> tree = new LinkedHashMap<Artifact,String>();
private int idSeq = 0;

void updateFrom(SerializableArtifactList clone) {
Map<String, Artifact> artifacts = new HashMap<>(); // need to share objects between tree and list, since computeDisplayName mutates displayPath
for (SerializableArtifact sa : clone) {
Artifact a = new Artifact(sa);
artifacts.put(a.relativePath, a);
add(a);
}
tree = new LinkedHashMap<>();
for (Map.Entry<SerializableArtifact, String> entry : clone.tree.entrySet()) {
SerializableArtifact sa = entry.getKey();
Artifact a = artifacts.get(sa.relativePath);
if (a == null) {
a = new Artifact(sa);
}
tree.put(a, entry.getValue());
}
}

public Map<Artifact,String> getTree() {
return tree;
Expand Down Expand Up @@ -1282,6 +1345,10 @@ public class Artifact {
*/
private String length;

Artifact(SerializableArtifact clone) {
this(clone.name, clone.relativePath, clone.href, clone.length, clone.treeNodeId);
}

/*package for test*/ Artifact(String name, String relativePath, String href, String len, String treeNodeId) {
this.name = name;
this.relativePath = relativePath;
Expand Down
21 changes: 8 additions & 13 deletions core/src/main/java/hudson/util/DirScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.Serializable;

import static hudson.Util.fixEmpty;
Expand All @@ -31,19 +30,15 @@ public abstract class DirScanner implements Serializable {
*/
protected final void scanSingle(File f, String relative, FileVisitor visitor) throws IOException {
if (visitor.understandsSymlink()) {
String target;
try {
String target;
try {
target = Util.resolveSymlink(f);
} catch (IOException x) { // JENKINS-13202
target = null;
}
if (target != null) {
visitor.visitSymlink(f, target, relative);
return;
}
} catch (InterruptedException e) {
throw (IOException) new InterruptedIOException().initCause(e);
target = Util.resolveSymlink(f);
} catch (IOException x) { // JENKINS-13202
target = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add logging while you are around?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferred to avoid making unrelated changes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

}
if (target != null) {
visitor.visitSymlink(f, target, relative);
return;
}
}
visitor.visit(f, relative);
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/hudson/util/IOUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public static boolean isAbsolute(String path) {
* 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.
*
* <p>If the file is symlink, the mode is that of the link target, not the link itself.
* @return a file mode, or -1 if not on Unix
* @throws PosixException if the file could not be statted, e.g. broken symlink
*/
Expand Down
Loading