diff --git a/.gitignore b/.gitignore
index 60ba89065c90d09f0cc1ea8935b0bf08b691b0e9..3cde4d575dc5ceede4290298480ac31d0eb8875b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,4 +49,5 @@ jenkins_*.changes
*.zip
push-build.sh
war/node_modules/
+war/yarn-error.log
.java-version
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a2d496cc2b211139d6f9b2eee27cb0be380ec8f5
--- /dev/null
+++ b/.mvn/extensions.xml
@@ -0,0 +1,7 @@
+
+
+ io.jenkins.tools.incrementals
+ git-changelist-maven-extension
+ 1.0-beta-4
+
+
diff --git a/.mvn/maven.config b/.mvn/maven.config
new file mode 100644
index 0000000000000000000000000000000000000000..e54fdacfe62468e4e85ebe51890384926933b476
--- /dev/null
+++ b/.mvn/maven.config
@@ -0,0 +1 @@
+-Pmight-produce-incrementals
diff --git a/BUILDING.TXT b/BUILDING.TXT
deleted file mode 100644
index bde5cadf353210259628baa34b1813f0acddc5b1..0000000000000000000000000000000000000000
--- a/BUILDING.TXT
+++ /dev/null
@@ -1,11 +0,0 @@
-If you want simply to have the jenkins.war as fast as possible (without test
-execution), run:
-
- mvn clean install -pl war -am -DskipTests
-
-The WAR file will be in war/target/jenkins.war (you can play with it)
-
-For more information on building Jenkins, visit
-https://wiki.jenkins-ci.org/display/JENKINS/Building+Jenkins
-
-Have Fun !!
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f99fe77067ef6c25597bf54df9e0debdaff27fef..ab12568a0a3b58e3b1789651e0d0f0d3343556df 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,115 @@
# Contributing to Jenkins
-For information on contributing to Jenkins, check out https://jenkins.io/redirect/contribute/. That page will help you get started with contributing to Jenkins.
+
+This page provides information about contributing code to the Jenkins core codebase.
+
+:exclamation: There's a lot more to the Jenkins project than just code. For information on contributing to the Jenkins project overall, check out https://jenkins.io/participate/.
+
+## Getting started
+
+1. Fork the repository on GitHub
+2. Clone the forked repository to your machine
+3. Install the development tools. In order to develop Jenkins, you need the following tools:
+ * Java Development Kit (JDK) 8.
+ - In Jenkins project we usually use [OpenJDK](http://openjdk.java.net/),
+ but you can use other JDKs as well.
+ - Java 9 is **not supported** in Jenkins.
+ * Maven 3.5.3 or above. You can download it [here](https://maven.apache.org/download.cgi)
+ * Any IDE which supports importing Maven projects
+4. Setup your development environment as described in [Preparing for Plugin Development](https://jenkins.io/doc/developer/tutorial/prepare/)
+
+If you want to contribute to Jenkins or just learn about the project,
+you can start by fixing some easier issues.
+In the Jenkins issue tracker we mark such issues as `newbie-friendly`.
+You can find them
+using [this query](https://issues.jenkins-ci.org/issues/?jql=project%20%3D%20JENKINS%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20component%20%3D%20core%20AND%20labels%20in%20(newbie-friendly)).
+
+## Building and Debugging
+
+The build flow for Jenkins core is built around Maven.
+Building and debugging process is described [here](https://jenkins.io/doc/developer/building/).
+
+If you want simply to have the `jenkins.war` file as fast as possible without tests, run:
+
+ mvn clean package -pl war -am -DskipTests -Dfindbugs.skip
+
+The WAR file will be created in `war/target/jenkins.war`.
+After that you can start Jenkins using Java CLI ([guide](https://wiki.jenkins.io/display/JENKINS/Starting+and+Accessing+Jenkins)).
+If you want to debug this WAR file without using Maven plugins,
+You can just start the executable with [Remote Debug Flags](https://stackoverflow.com/questions/975271/remote-debugging-a-java-application)
+and then attach IDE Debugger to it.
+
+## Testing changes
+
+Jenkins core includes unit and functional tests as a part of the repository.
+
+Functional tests (`test` module) take a while even on server-grade machines.
+Most of the tests will be launched by the continuous integration instance,
+so there is no strict need to run full test suites before proposing a pull request.
+
+There are 3 profiles for tests:
+
+* `light-test` - only unit tests, no functional tests
+* `smoke-test` - run unit tests + a number of functional tests
+* `all-tests` - Runs all tests, with re-run (default)
+
+In addition to the included tests, you can also find extra integration and UI
+tests in the [Acceptance Test Harness (ATH)](https://github.com/jenkinsci/acceptance-test-harness) repository.
+If you propose complex UI changes, you should create new ATH tests for them.
+
+## Proposing Changes
+
+The Jenkins project source code repositories are hosted GitHub.
+All proposed changes are submitted and code reviewed using the _GitHub Pull Request_ process.
+
+To submit a pull request:
+
+1. Commit changes and push them to your fork on GitHub.
+It is a good practice is to create branches instead of pushing to master.
+2. In GitHub Web UI click the _New Pull Request_ button
+3. Select `jenkinsci` as _base fork_ and `master` as `base`, then click _Create Pull Request_
+ * We integrate all changes into the master branch towards the Weekly releases
+ * After that the changes may be backported to the current LTS baseline by the LTS Team.
+ The backporting process is described [here](https://jenkins.io/download/lts/).
+4. Fill in the Pull Request description according to the [proposed template](.github/PULL_REQUEST_TEMPLATE.md).
+5. Click _Create Pull Request_
+6. Wait for CI results/reviews, process the feedback.
+ * If you do not get feedback after 3 days, feel free to ping `@jenkinsci/code-reviewers` to CC.
+ * Usually we merge pull requests after 2 votes from reviewers or after 1 vote and 1-week delay without extra reviews.
+
+Once your Pull Request is ready to be merged,
+the repository maintainers will integrate it, prepare changelogs and
+ensure it gets released in one of incoming Weekly releases.
+There is no additional action required from pull request authors at this point.
+
+## Copyright
+
+Jenkins core is licensed under [MIT license](./LICENSE.txt), with a few exceptions in bundled classes.
+We consider all contributions as MIT unless it's explicitly stated otherwise.
+MIT-incompatible code contributions will be rejected.
+Contributions under MIT-compatible licenses may be also rejected if they are not ultimately necessary.
+
+We **Do NOT** require pull request submitters to sign the [contributor agreement](https://wiki.jenkins.io/display/JENKINS/Copyright+on+source+code)
+as long as the code is licensed under MIT and merged by one of the contributors with the signed agreement.
+
+We still encourage people to sign the contributor agreement if they intend to submit more than a few pull requests.
+Signing is also a mandatory prerequisite for getting merge/push permissions to core repositories
+and for joining teams like [Jenkins Security Team](https://jenkins.io/security/#team).
+
+## Continuous Integration
+
+The Jenkins project has a Continuous Integration server... powered by Jenkins, of course.
+It is located at [ci.jenkins.io](https://ci.jenkins.io/).
+
+The Jenkins project uses [Jenkins Pipeline](https://jenkins.io/doc/book/pipeline/) to run builds.
+The code for the core build flow is stored in the [Jenkinsfile](./Jenkinsfile) in the repository root.
+If you want to update that build flow (e.g. "add more checks"),
+just submit a pull request.
+
+# Links
+
+* [Jenkins Contribution Landing Page](https://jenkins.io/participate/)
+* [Jenkins IRC Channel](https://jenkins.io/chat/)
+* [Beginners Guide To Contributing](https://wiki.jenkins.io/display/JENKINS/Beginners+Guide+to+Contributing)
+* [List of newbie-friendly issues in the core](https://issues.jenkins-ci.org/issues/?jql=project%20%3D%20JENKINS%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20component%20%3D%20core%20AND%20labels%20in%20(newbie-friendly))
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d4541bdfcbaa7904cfc9264f6a823d80d1b55992
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,26 @@
+# This is a Dockerfile definition for Experimental Docker builds.
+# DockerHub: https://hub.docker.com/r/jenkins/jenkins-experimental/
+# If you are looking for official images, see https://github.com/jenkinsci/docker
+FROM maven:3.5.4-jdk-8 as builder
+
+COPY .mvn/ /jenkins/src/.mvn/
+COPY cli/ /jenkins/src/cli/
+COPY core/ /jenkins/src/core/
+COPY src/ /jenkins/src/src/
+COPY test/ /jenkins/src/test/
+COPY war/ /jenkins/src/war/
+COPY *.xml /jenkins/src/
+COPY LICENSE.txt /jenkins/src/LICENSE.txt
+COPY licenseCompleter.groovy /jenkins/src/licenseCompleter.groovy
+COPY show-pom-version.rb /jenkins/src/show-pom-version.rb
+
+WORKDIR /jenkins/src/
+RUN mvn clean install --batch-mode -Plight-test
+
+# The image is based on the previous weekly, new changes in jenkinci/docker are not applied
+FROM jenkins/jenkins:latest
+
+LABEL Description="This is an experimental image for the master branch of the Jenkins core" Vendor="Jenkins Project"
+
+COPY --from=builder /jenkins/src/war/target/jenkins.war /usr/share/jenkins/jenkins.war
+ENTRYPOINT ["tini", "--", "/usr/local/bin/jenkins.sh"]
diff --git a/Jenkinsfile b/Jenkinsfile
index bcced2b016045b0c7be4012dc213c1ccbdc9d6da..f6eb3e2c0cfdc06c586ea5c1a1a46689b9811957 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -13,15 +13,18 @@
def runTests = true
def failFast = false
-properties([buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '20'))])
+properties([buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '20')), durabilityHint('PERFORMANCE_OPTIMIZED')])
// see https://github.com/jenkins-infra/documentation/blob/master/ci.adoc for information on what node types are available
def buildTypes = ['Linux', 'Windows']
+def jdks = [8]
def builds = [:]
for(i = 0; i < buildTypes.size(); i++) {
+for(j = 0; j < jdks.size(); j++) {
def buildType = buildTypes[i]
- builds[buildType] = {
+ def jdk = jdks[j]
+ builds["${buildType}-jdk${jdk}"] = {
node(buildType.toLowerCase()) {
timestamps {
// First stage is actually checking out the source. Since we're using Multibranch
@@ -30,16 +33,20 @@ for(i = 0; i < buildTypes.size(); i++) {
checkout scm
}
+ def changelistF = "${pwd tmp: true}/changelist"
+ def m2repo = "${pwd tmp: true}/m2repo"
+
// Now run the actual build.
stage("${buildType} Build / Test") {
timeout(time: 180, unit: 'MINUTES') {
// See below for what this method does - we're passing an arbitrary environment
// variable to it so that JAVA_OPTS and MAVEN_OPTS are set correctly.
withMavenEnv(["JAVA_OPTS=-Xmx1536m -Xms512m",
- "MAVEN_OPTS=-Xmx1536m -Xms512m"]) {
+ "MAVEN_OPTS=-Xmx1536m -Xms512m"], jdk) {
// Actually run Maven!
// -Dmaven.repo.local=… tells Maven to create a subdir in the temporary directory for the local Maven repository
- def mvnCmd = "mvn -Pdebug -U clean install javadoc:javadoc ${runTests ? '-Dmaven.test.failure.ignore' : '-DskipTests'} -V -B -Dmaven.repo.local=${pwd tmp: true}/m2repo -s settings-azure.xml -e"
+ def mvnCmd = "mvn -Pdebug -U -Dset.changelist help:evaluate -Dexpression=changelist -Doutput=$changelistF clean install ${runTests ? '-Dmaven.test.failure.ignore' : '-DskipTests'} -V -B -Dmaven.repo.local=$m2repo -s settings-azure.xml -e"
+
if(isUnix()) {
sh mvnCmd
sh 'test `git status --short | tee /dev/stderr | wc --bytes` -eq 0'
@@ -51,34 +58,63 @@ for(i = 0; i < buildTypes.size(); i++) {
}
// Once we've built, archive the artifacts and the test results.
- stage("${buildType} Archive Artifacts / Test Results") {
- def files = findFiles(glob: '**/target/*.jar, **/target/*.war, **/target/*.hpi')
- renameFiles(files, buildType.toLowerCase())
-
- archiveArtifacts artifacts: '**/target/*.jar, **/target/*.war, **/target/*.hpi',
- fingerprint: true
+ stage("${buildType} Publishing") {
if (runTests) {
- junit healthScaleFactor: 20.0, testResults: '**/target/surefire-reports/*.xml'
+ junit healthScaleFactor: 20.0, testResults: '*/target/surefire-reports/*.xml'
+ }
+ if (buildType == 'Linux') {
+ def changelist = readFile(changelistF)
+ dir(m2repo) {
+ archiveArtifacts artifacts: "**/*$changelist/*$changelist*",
+ excludes: '**/*.lastUpdated,**/jenkins-test/',
+ allowEmptyArchive: true, // in case we forgot to reincrementalify
+ fingerprint: true
+ }
}
}
}
}
}
+}}
+
+// TODO: ATH flow now supports Java 8 only, it needs to be reworked (INFRA-1690)
+builds.ath = {
+ node("docker&&highmem") {
+ // Just to be safe
+ deleteDir()
+ def fileUri
+ def metadataPath
+ dir("sources") {
+ checkout scm
+ withMavenEnv(["JAVA_OPTS=-Xmx1536m -Xms512m",
+ "MAVEN_OPTS=-Xmx1536m -Xms512m"], 8) {
+ sh "mvn --batch-mode --show-version -DskipTests -am -pl war package -Dmaven.repo.local=${pwd tmp: true}/m2repo -s settings-azure.xml"
+ }
+ dir("war/target") {
+ fileUri = "file://" + pwd() + "/jenkins.war"
+ }
+ metadataPath = pwd() + "/essentials.yml"
+ }
+ dir("ath") {
+ runATH jenkins: fileUri, metadataFile: metadataPath
+ }
+ }
}
builds.failFast = failFast
parallel builds
+infra.maybePublishIncrementals()
// This method sets up the Maven and JDK tools, puts them in the environment along
// with whatever other arbitrary environment variables we passed in, and runs the
// body we passed in within that environment.
-void withMavenEnv(List envVars = [], def body) {
+void withMavenEnv(List envVars = [], def javaVersion, def body) {
// The names here are currently hardcoded for my test environment. This needs
// to be made more flexible.
// Using the "tool" Workflow call automatically installs those tools on the
// node.
String mvntool = tool name: "mvn", type: 'hudson.tasks.Maven$MavenInstallation'
- String jdktool = tool name: "jdk8", type: 'hudson.model.JDK'
+ String jdktool = tool name: "jdk${javaVersion}", type: 'hudson.model.JDK'
// Set JAVA_HOME, MAVEN_HOME and special PATH variables for the tools we're
// using.
@@ -92,15 +128,3 @@ void withMavenEnv(List envVars = [], def body) {
body.call()
}
}
-
-void renameFiles(def files, String prefix) {
- for(i = 0; i < files.length; i++) {
- def newPath = files[i].path.replace(files[i].name, "${prefix}-${files[i].name}")
- def rename = "${files[i].path} ${newPath}"
- if(isUnix()) {
- sh "mv ${rename}"
- } else {
- bat "move ${rename}"
- }
- }
-}
diff --git a/cli/pom.xml b/cli/pom.xml
index d5aa1608e7024213a069ccbf9c2f98fe142b2a6e..670e416a26dd5c5f5bc6943ad046e9f8c4c41c00 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -4,8 +4,8 @@
org.jenkins-ci.main
- pom
- 2.73.4-SNAPSHOT
+ jenkins-parent
+ ${revision}${changelist}cli
@@ -13,6 +13,11 @@
Jenkins cliCommand line interface for Jenkins
+
+
+ Medium
+
+
org.powermock
@@ -21,17 +26,20 @@
org.powermock
- powermock-api-mockito
+ powermock-api-mockito2testorg.kohsukeaccess-modifier-annotation
+
+ org.jenkins-ci
+ annotation-indexer
+ commons-codeccommons-codec
- 1.4commons-io
@@ -55,7 +63,7 @@
org.apache.sshdsshd-core
- 1.6.0
+ 1.7.0true
@@ -68,6 +76,15 @@
trilead-ssh2build214-jenkins-1
+
+ com.google.code.findbugs
+ annotations
+ provided
+
+
+ commons-lang
+ commons-lang
+
@@ -78,17 +95,19 @@
- attached
+ singlepackage
- jar-with-dependencies
+
+ jar-with-dependencies
+ hudson.cli.CLI
- ${build.version}
+ ${project.version}
diff --git a/cli/src/filter/resources/jenkins/cli/jenkins-cli-version.properties b/cli/src/filter/resources/jenkins/cli/jenkins-cli-version.properties
index 39b91895a20a85254399d1f4f0206c6af45ff094..defbd48204e4784090fe0e17e28fe5bc395abf47 100644
--- a/cli/src/filter/resources/jenkins/cli/jenkins-cli-version.properties
+++ b/cli/src/filter/resources/jenkins/cli/jenkins-cli-version.properties
@@ -1 +1 @@
-version=${build.version}
+version=${project.version}
diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java
index 0d47f36ff90f73125fe6c04f9045af76a42c3a44..52c6785d5871d2e447b6747fd98eb88e1ff50bb9 100644
--- a/cli/src/main/java/hudson/cli/CLI.java
+++ b/cli/src/main/java/hudson/cli/CLI.java
@@ -77,6 +77,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.*;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
/**
* CLI entry point to Jenkins.
@@ -452,6 +453,10 @@ public class CLI implements AutoCloseable {
String user = null;
String auth = null;
+
+ String userIdEnv = System.getenv("JENKINS_USER_ID");
+ String tokenEnv = System.getenv("JENKINS_API_TOKEN");
+
boolean strictHostKey = false;
while(!args.isEmpty()) {
@@ -549,7 +554,7 @@ public class CLI implements AutoCloseable {
for (Handler h : Logger.getLogger("").getHandlers()) {
h.setLevel(level);
}
- for (Logger logger : new Logger[] {LOGGER, PlainCLIProtocol.LOGGER, Logger.getLogger("org.apache.sshd")}) { // perhaps also Channel
+ for (Logger logger : new Logger[] {LOGGER, FullDuplexHttpStream.LOGGER, PlainCLIProtocol.LOGGER, Logger.getLogger("org.apache.sshd")}) { // perhaps also Channel
logger.setLevel(level);
}
args = args.subList(2, args.size());
@@ -563,6 +568,17 @@ public class CLI implements AutoCloseable {
return -1;
}
+ if (auth == null) {
+ // -auth option not set
+ if (StringUtils.isNotBlank(userIdEnv) && StringUtils.isNotBlank(tokenEnv)) {
+ auth = StringUtils.defaultString(userIdEnv).concat(":").concat(StringUtils.defaultString(tokenEnv));
+ } else if (StringUtils.isNotBlank(userIdEnv) || StringUtils.isNotBlank(tokenEnv)) {
+ printUsage(Messages.CLI_BadAuth());
+ return -1;
+ } // Otherwise, none credentials were set
+
+ }
+
if (!url.endsWith("/")) {
url += '/';
}
@@ -809,9 +825,14 @@ public class CLI implements AutoCloseable {
return authenticate(Collections.singleton(key));
}
+ /** For access from {@code HelpCommand}. */
+ static String usage() {
+ return Messages.CLI_Usage();
+ }
+
private static void printUsage(String msg) {
if(msg!=null) System.out.println(msg);
- System.err.println(Messages.CLI_Usage());
+ System.err.println(usage());
}
static final Logger LOGGER = Logger.getLogger(CLI.class.getName());
diff --git a/cli/src/main/java/hudson/cli/Connection.java b/cli/src/main/java/hudson/cli/Connection.java
index 017051a64a646a76b4ecfe81966381e3874adbca..eacc567a7e5d822afc9e4d1b20afc5473cbb0b3b 100644
--- a/cli/src/main/java/hudson/cli/Connection.java
+++ b/cli/src/main/java/hudson/cli/Connection.java
@@ -55,6 +55,7 @@ import java.security.Signature;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
+import org.jenkinsci.remoting.util.AnonymousClassWarnings;
/**
* Used by Jenkins core only in deprecated Remoting-based CLI.
@@ -102,7 +103,7 @@ public class Connection {
* Sends a serializable object.
*/
public void writeObject(Object o) throws IOException {
- ObjectOutputStream oos = new ObjectOutputStream(out);
+ ObjectOutputStream oos = AnonymousClassWarnings.checkingObjectOutputStream(out);
oos.writeObject(o);
// don't close oss, which will close the underlying stream
// no need to flush either, given the way oos is implemented
diff --git a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java
index ef8bd5ff12cea23a0906ce9e07a6fa7843973d70..9b18142b7824257af32e510e4eb99d5cc677cf69 100644
--- a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java
+++ b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java
@@ -1,9 +1,7 @@
package hudson.cli;
-import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
@@ -16,7 +14,7 @@ import org.apache.commons.codec.binary.Base64;
/**
* Creates a capacity-unlimited bi-directional {@link InputStream}/{@link OutputStream} pair over
* HTTP, which is a request/response protocol.
- *
+ * {@code FullDuplexHttpService} is the counterpart on the server side.
* @author Kohsuke Kawaguchi
*/
public class FullDuplexHttpStream {
@@ -29,10 +27,18 @@ public class FullDuplexHttpStream {
private final OutputStream output;
private final InputStream input;
+ /**
+ * A way to get data from the server.
+ * There will be an initial zero byte used as a handshake which you should expect and ignore.
+ */
public InputStream getInputStream() {
return input;
}
+ /**
+ * A way to upload data to the server.
+ * You will need to write to this and {@link OutputStream#flush} it to finish establishing a connection.
+ */
public OutputStream getOutputStream() {
return output;
}
@@ -79,11 +85,10 @@ public class FullDuplexHttpStream {
URL target = new URL(this.base, relativeTarget);
- CrumbData crumbData = new CrumbData();
-
UUID uuid = UUID.randomUUID(); // so that the server can correlate those two connections
// server->client
+ LOGGER.fine("establishing download side");
HttpURLConnection con = (HttpURLConnection) target.openConnection();
con.setDoOutput(true); // request POST to avoid caching
con.setRequestMethod("POST");
@@ -92,17 +97,16 @@ public class FullDuplexHttpStream {
if (authorization != null) {
con.addRequestProperty("Authorization", authorization);
}
- if(crumbData.isValid) {
- con.addRequestProperty(crumbData.crumbName, crumbData.crumb);
- }
con.getOutputStream().close();
input = con.getInputStream();
- // make sure we hit the right URL
+ // make sure we hit the right URL; no need for CLI.verifyJenkinsConnection here
if (con.getHeaderField("Hudson-Duplex") == null) {
throw new CLI.NotTalkingToJenkinsException("There's no Jenkins running at " + target + ", or is not serving the HTTP Duplex transport");
}
+ LOGGER.fine("established download side"); // calling getResponseCode or getHeaderFields breaks everything
- // client->server uses chunked encoded POST for unlimited capacity.
+ // client->server uses chunked encoded POST for unlimited capacity.
+ LOGGER.fine("establishing upload side");
con = (HttpURLConnection) target.openConnection();
con.setDoOutput(true); // request POST
con.setRequestMethod("POST");
@@ -113,11 +117,8 @@ public class FullDuplexHttpStream {
if (authorization != null) {
con.addRequestProperty ("Authorization", authorization);
}
-
- if(crumbData.isValid) {
- con.addRequestProperty(crumbData.crumbName, crumbData.crumb);
- }
output = con.getOutputStream();
+ LOGGER.fine("established upload side");
}
// As this transport mode is using POST, it is necessary to resolve possible redirections using GET first.
@@ -141,59 +142,4 @@ public class FullDuplexHttpStream {
static final int BLOCK_SIZE = 1024;
static final Logger LOGGER = Logger.getLogger(FullDuplexHttpStream.class.getName());
- private final class CrumbData {
- String crumbName;
- String crumb;
- boolean isValid;
-
- private CrumbData() {
- this.crumbName = "";
- this.crumb = "";
- this.isValid = false;
- getData();
- }
-
- private void getData() {
- try {
- String base = createCrumbUrlBase();
- String[] pair = readData(base + "?xpath=concat(//crumbRequestField,\":\",//crumb)").split(":", 2);
- crumbName = pair[0];
- crumb = pair[1];
- isValid = true;
- LOGGER.fine("Crumb data: "+crumbName+"="+crumb);
- } catch (IOException e) {
- // presumably this Hudson doesn't use crumb
- LOGGER.log(Level.FINE,"Failed to get crumb data",e);
- }
- }
-
- private String createCrumbUrlBase() {
- return base + "crumbIssuer/api/xml/";
- }
-
- private String readData(String dest) throws IOException {
- HttpURLConnection con = (HttpURLConnection) new URL(dest).openConnection();
- if (authorization != null) {
- con.addRequestProperty("Authorization", authorization);
- }
- CLI.verifyJenkinsConnection(con);
-
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
- String line = reader.readLine();
- String nextLine = reader.readLine();
- if (nextLine != null) {
- System.err.println("Warning: received junk from " + dest);
- System.err.println(line);
- System.err.println(nextLine);
- while ((nextLine = reader.readLine()) != null) {
- System.err.println(nextLine);
- }
- }
- return line;
- }
- finally {
- con.disconnect();
- }
- }
- }
}
diff --git a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
index d7753a750498aff78325fc04326eb63b7e45209d..9f87dd4a7b38a174229ccfc02b04779b51002951 100644
--- a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
+++ b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
@@ -71,7 +71,7 @@ public class PrivateKeyProvider {
/**
* Read keys from default keyFiles
*
- * .ssh/id_rsa, .ssh/id_dsa and .ssh/identity.
+ * {@code .ssh/id_rsa}, {@code .ssh/id_dsa} and {@code .ssh/identity}.
*
* @return true if some key was read successfully.
*/
diff --git a/cli/src/main/java/hudson/cli/SSHCLI.java b/cli/src/main/java/hudson/cli/SSHCLI.java
index bdd6c2beb50e5e696af3467e5bae7598f41eb5ef..39dc9210a3e1615bac888932c689d97ad4dd1b4d 100644
--- a/cli/src/main/java/hudson/cli/SSHCLI.java
+++ b/cli/src/main/java/hudson/cli/SSHCLI.java
@@ -51,6 +51,8 @@ import org.apache.sshd.common.util.io.NoCloseInputStream;
import org.apache.sshd.common.util.io.NoCloseOutputStream;
import org.apache.sshd.common.util.security.SecurityUtils;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
/**
* Implements SSH connection mode of {@link CLI}.
* In a separate class to avoid any class loading of {@code sshd-core} when not using {@code -ssh} mode.
@@ -59,6 +61,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
*/
class SSHCLI {
+ @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE", justification = "Due to whatever reason FindBugs reports it fot try-with-resources")
static int sshConnection(String jenkinsUrl, String user, List args, PrivateKeyProvider provider, final boolean strictHostKey) throws IOException {
Logger.getLogger(SecurityUtils.class.getName()).setLevel(Level.WARNING); // suppress: BouncyCastle not registered, using the default JCE provider
URL url = new URL(jenkinsUrl + "login");
diff --git a/cli/src/main/resources/hudson/cli/client/Messages.properties b/cli/src/main/resources/hudson/cli/client/Messages.properties
index 921fe67a211da560ac75fc355179a117b3c00dc2..2e7fde44cb075a87813b4cced7eb48f2952c85cd 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages.properties
@@ -1,22 +1,25 @@
CLI.Usage=Jenkins CLI\n\
Usage: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
Options:\n\
- -s URL : the server URL (defaults to the JENKINS_URL env var)\n\
- -http : use a plain CLI protocol over HTTP(S) (the default; mutually exclusive with -ssh and -remoting)\n\
- -ssh : use SSH protocol (requires -user; SSH port must be open on server, and user must have registered a public key)\n\
- -remoting : use deprecated Remoting channel protocol (if enabled on server; for compatibility with legacy commands or command modes only)\n\
- -i KEY : SSH private key file used for authentication (for use with -ssh or -remoting)\n\
- -p HOST:PORT : HTTP proxy host and port for HTTPS proxy tunneling. See https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
- -noCertificateCheck : bypass HTTPS certificate check entirely. Use with caution\n\
- -noKeyAuth : don't try to load the SSH authentication private key. Conflicts with -i\n\
- -user : specify user (for use with -ssh)\n\
- -strictHostKey : request strict host key checking (for use with -ssh)\n\
- -logger FINE : enable detailed logging from the client\n\
- -auth [ USER:SECRET | @FILE ] : specify username and either password or API token (or load from them both from a file);\n\
- for use with -http, or -remoting but only when the JNLP agent port is disabled\n\
+ \ -s URL : the server URL (defaults to the JENKINS_URL env var)\n\
+ \ -http : use a plain CLI protocol over HTTP(S) (the default; mutually exclusive with -ssh and -remoting)\n\
+ \ -ssh : use SSH protocol (requires -user; SSH port must be open on server, and user must have registered a public key)\n\
+ \ -remoting : use deprecated Remoting channel protocol (if enabled on server; for compatibility with legacy commands or command modes only)\n\
+ \ -i KEY : SSH private key file used for authentication (for use with -ssh or -remoting)\n\
+ \ -p HOST:PORT : HTTP proxy host and port for HTTPS proxy tunneling. See https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+ \ -noCertificateCheck : bypass HTTPS certificate check entirely. Use with caution\n\
+ \ -noKeyAuth : don't try to load the SSH authentication private key. Conflicts with -i\n\
+ \ -user : specify user (for use with -ssh)\n\
+ \ -strictHostKey : request strict host key checking (for use with -ssh)\n\
+ \ -logger FINE : enable detailed logging from the client\n\
+ \ -auth [ USER:SECRET | @FILE ] : specify username and either password or API token (or load from them both from a file);\n\
+ \ for use with -http, or -remoting but only when the JNLP agent port is disabled.\n\
+ \ Passing crendentials by a file is recommended.\n\
+ \ See https://jenkins.io/redirect/cli-http-connection-mode for more info and options.\n\
\n\
- The available commands depend on the server. Run the 'help' command to\n\
+ The available commands depend on the server. Run the 'help' command to \
see the list.
CLI.NoURL=Neither -s nor the JENKINS_URL env var is specified.
CLI.VersionMismatch=Version mismatch. This CLI cannot work with this Jenkins server.
CLI.NoSuchFileExists=No such file exists: {0}
+CLI.BadAuth=The JENKINS_USER_ID and JENKINS_API_TOKEN env vars should be both set or left empty.
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_es.properties b/cli/src/main/resources/hudson/cli/client/Messages_es.properties
index f9979a5619831289dfb4952ffe328e4d6d6b9798..b8ff42b3c23b707bdd8b105f53a624e034ebcc7d 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages_es.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages_es.properties
@@ -1,10 +1,24 @@
CLI.Usage=Jenkins CLI\n\
- Usar: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
- Options:\n\
- \ -s URL : direccin web (por defecto se usa la variable JENKINS_URL)\n\
+ Uso: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
+ Opciones:\n\
+ -s URL : direcci\u00f3n web (por defecto se usa la variable JENKINS_URL)\n\
+ -http : usa un protocolo plano sobre HTTP(S) (es el uso por defecto; excluyente con -ssh y -remoting)\n\
+ -ssh : usa el protocolo SSH (requiere -user; el puerto SSH debe estar abierto en el servidor y el usuario debe tener registrada su clave publica)\n\
+ -remoting : usa el protocolo deprecado de Remoting (siempre que est\u00e9 habilitado en el servidor; s\u00f3lo para compatibilidad con comandos heredados o legacy)\n\
+ -i KEY : clave privada SSH usada para autenticaci\u00f3n (usado con -ssh o -remoting)\n\
+ -p HOST:PORT : host y puerto para el uso de proxy HTTPS. Ver https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+ -noCertificateCheck : elude por completo la verificaci\u00f3n del certificado HTTPS. Usar con precauci\u00f3n\n\
+ -noKeyAuth : intenta no cargar la clave privada de autenticaci\u00f3n SSH. Presenta conflicto con -i\n\
+ -user : especifica el usuario (se usa con -ssh)\n\
+ -strictHostKey : solicita la comprobaci\u00f3n estricta de la clave del host (se usa con -ssh)\n\
+ -logger FINE : habilita logs detallados en el cliente\n\
+ -auth [ USER:SECRET | @FILE ] : especifica el usuario y contrase\u00f1a o API token (o los carga de un fichero);\n\
+ se usa con -http o -remoting pero s\u00f3lo si el puerto del agente JNLP est\u00e1 deshabilitado.\n\
+ No es necesario si las variables de entorno JENKINS_USER_ID y JENKINS_API_TOKEN se encuentran configuradas.\n\
\n\
- La lista completa de comandos disponibles depende del servidor. Ejecuta\n\
- el comando ''help'' para ver la lista.
-CLI.NoURL=No se ha especificado el parmetro -s ni la variable JENKINS_URL
-CLI.VersionMismatch=La versin no coincide. Esta consola "CLI" no se puede usar en este Jenkins
+ La lista de comandos disponibles depende del servidor. Ejecuta\n\
+ el comando ''help'' para ver la lista completa.
+CLI.NoURL=No se ha especificado el par\u00e1metro -s ni la variable JENKINS_URL
+CLI.VersionMismatch=La versi\u00f3n no coincide. Esta consola "CLI" no se puede usar en este Jenkins
CLI.NoSuchFileExists=El fichero {0} no existe.
+CLI.BadAuth=Ambas variables de entorno JENKINS_USER_ID y JENKINS_API_TOKEN deber\u00edan estar configuradas o mantenerse vac\u00edas.
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_it.properties b/cli/src/main/resources/hudson/cli/client/Messages_it.properties
new file mode 100644
index 0000000000000000000000000000000000000000..6236107d9c7cff3786ea619aba6097291835d6f7
--- /dev/null
+++ b/cli/src/main/resources/hudson/cli/client/Messages_it.properties
@@ -0,0 +1,22 @@
+CLI.NoSuchFileExists=File non esistente: {0}
+CLI.NoURL=Non sono stati specificati n -s n la variabile d''ambiente JENKINS_URL.
+CLI.VersionMismatch=Le versioni non corrispondono. Quest''interfaccia della riga di comando non pu funzionare con questo server Jenkins.
+CLI.Usage=Interfaccia della riga di comando di Jenkins\n\
+Uso: java -jar jenkins-cli.jar [-s URL] comando [opzioni...] argomenti...\n\
+Opzioni:\n\
+-s URL : l''URL del server (impostazione predefinita: la variabile d''ambiente JENKINS_URL)\n\
+-http : utilizza un protocollo interfaccia della riga di comando in testo semplice su HTTP(S) (impostazione predefinita; mutualmente esclusiva con -ssh e -remoting)\n\
+-ssh : utilizza il protocollo SSH (richiede -user; la porta SSH deve essere aperta sul server e l''utente deve aver registrato una chiave pubblica)\n\
+-remoting : utilizza il protocollo deprecato Remoting channel (se abilitato sul server; solo per compatibilit con comandi o modalit comandi legacy)\n\
+-i KEY : file chiave privata SSH utilizzato per l''autenticazione (per l''utilizzo con -ssh o -remoting)\n\
+-p HOST:PORTA : host proxy HTTP e porta per il tunneling proxy HTTPS. Vedi https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+-noCertificateCheck : ometti completamente il controllo dei certificati HTTPS. Utilizzare con cautela\n\
+-noKeyAuth : non tentare di caricare la chiave privata per l''autenticazione SSH. In conflitto con -i\n\
+-user : specifica l''utente (per l''utilizzo con -ssh)\n\
+-strictHostKey : richiedi la modalit strict per il controllo delle chiavi host (per l''utilizzo con -ssh)\n\
+-logger FINE : abilita la registrazione dettagliata da parte del client\n\
+-auth [ UTENTE:SEGRETO | @FILE ] : specifica il nome utente e la password o il token API (o caricali entrambi da un file);\n\
+per l''utilizzo con -http, o -remoting ma solo quando la porta agente JNLP disabilitata\n\
+\n\
+I comandi disponibili dipendono dal server. Eseguire il comando ''help'' per\n\
+visualizzarne l''elenco.
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties b/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties
new file mode 100644
index 0000000000000000000000000000000000000000..16fa41c48c9a4aad3d63e9f40e2d017b3bd9fb77
--- /dev/null
+++ b/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties
@@ -0,0 +1,44 @@
+# The MIT License
+#
+# Copyright (c) 2013, Sun Microsystems, Inc., Kohsuke Kawaguchi, Pei-Tang Huang,
+# Chunghwa Telecom Co., Ltd., and a number of other of contributers
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+CLI.Usage=Jenkins CLI\n\
+ \u7528\u6cd5: java -jar jenkins-cli.jar [-s url] \u547d\u4ee4 [\u9009\u9879...] \u53c2\u6570...\n\
+ \u9009\u9879:\n\
+ -s URL : \u670d\u52a1\u5668url \uff08\u9ed8\u8ba4\u662fjenkins_url\u73af\u5883\u53d8\u91cf\uff09\n\
+ -http : \u5728http%28s%29\u4e0a\u4f7f\u7528\u539f\u59cb\u7684cli\u534f\u8bae\uff08\u9ed8\u8ba4\u503c%3b \u4e0e -ssh \u548c -remoting \u4e92\u65a5\uff09\n\
+ -ssh : \u4f7f\u7528ssh\u534f\u8bae\uff08\u9700\u8981 -user%3b \u670d\u52a1\u5668\u7684ssh\u7aef\u53e3\u5fc5\u987b\u6253\u5f00\uff0c\u4e14\u7528\u6237\u5fc5\u987b\u5df2\u6ce8\u518c\u516c\u94a5\u3002\uff09\n\
+ -remoting : \u4f7f\u7528\u4e0d\u63a8\u8350\u7684\u8fdc\u7a0b\u4fe1\u9053\u534f\u8bae \uff08\u5982\u679c\u670d\u52a1\u5668\u4e0a\u662f\u6253\u5f00\u7684\uff1b\u4ec5\u7528\u4e8e\u517c\u5bb9\u9057\u7559\u547d\u4ee4\u6216\u547d\u4ee4\u6a21\u5f0f\uff09\n\
+ -i KEY : \u7528\u4e8e\u8ba4\u8bc1\u7684ssh\u79c1\u94a5\u6587\u4ef6\uff08\u4e0e -ssh \u6216 -remoting \u4e00\u8d77\u4f7f\u7528\uff09\n\
+ -p HOST:PORT : \u7528\u6237https\u4ee3\u7406\u96a7\u9053\u7684http\u4ee3\u7406\u4e3b\u673a\u548c\u7aef\u53e3\u3002\u53c2\u89c1 https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+ -noCertificateCheck : \u5b8c\u5168\u5ffd\u7565https\u8bc1\u4e66\u8ba4\u8bc1\u3002\u8c28\u614e\u4f7f\u7528\n\
+ -noKeyAuth : \u65e0\u9700\u5c1d\u8bd5\u52a0\u8f7dssh\u8ba4\u8bc1\u79c1\u94a5\u3002\u4e0e -i \u76f8\u53cd\n\
+ -user : \u6307\u5b9a\u7528\u6237\uff08\u4e0e -ssh \u4e00\u8d77\u4f7f\u7528\uff09\n\
+ -strictHostKey : \u8981\u6c42\u9a8c\u8bc1\u4e3b\u673akey\u68c0\u67e5\uff08\u4e0e -ssh \u4e00\u8d77\u4f7f\u7528\uff09\n\
+ -logger FINE : \u5141\u8bb8\u5ba2\u6237\u7aef\u8be6\u7ec6\u65e5\u5fd7\u8bb0\u5f55\n\
+ -auth [ USER:SECRET | @FILE ] : \u6307\u5b9a\u7528\u6237\u540d\u4e0e\u5bc6\u7801\u6216\u7528\u6237\u540d\u4e0eapi token\uff08\u6216\u8005\u4ece\u6587\u4ef6\u52a0\u8f7d\uff09\uff1b\n\
+ \u4e0e -http \u4e00\u8d77\u4f7f\u7528\uff0c\u6216\u8005\u53ea\u5728jnlp\u4ee3\u7406\u7aef\u53e3\u7981\u7528\u65f6\u4e0e -remoting \u4e00\u8d77\u4f7f\u7528\n\
+ \n\
+ \u53ef\u7528\u7684\u547d\u4ee4\u53d6\u51b3\u4e8e\u670d\u52a1\u5668\u3002\u6267\u884c 'help' \u547d\u4ee4\u53ef\u4ee5\u67e5\u770b\u5b8c\u6574\u6e05\u5355\u3002
+CLI.NoURL=\u6c92\u6709\u6307\u5b9a -s \u53c2\u6570\u6216\u8005 jenkins_url \u73af\u5883\u53d8\u91cf\u3002
+CLI.VersionMismatch=\u7248\u672c\u4e0d\u5339\u914d\u3002cli \u65e0\u6cd5\u5728\u6b64 jenkins \u670d\u52a1\u5668\u4e0a\u8fd0\u884c\u3002
+CLI.NoSuchFileExists=\u6587\u4ef6\u4e0d\u5b58\u5728: {0}
diff --git a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
index 86970265089b4bf41cb7bc4aae7d6e320447d21a..a29af91e6eb818245140e987dda1c323786a3fd8 100644
--- a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
+++ b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
@@ -47,7 +47,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
-@PrepareForTest(CLI.class) // When mocking new operator caller has to be @PreparedForTest, not class itself
+@PrepareForTest({CLI.class, CLIConnectionFactory.class}) // When mocking new operator caller has to be @PreparedForTest, not class itself
public class PrivateKeyProviderTest {
@Test
@@ -113,15 +113,9 @@ public class PrivateKeyProviderTest {
private Iterable withKeyPairs(final KeyPair... expected) {
return Mockito.argThat(new ArgumentMatcher>() {
- @Override public void describeTo(Description description) {
- description.appendText(Arrays.asList(expected).toString());
- }
-
- @Override public boolean matches(Object argument) {
- if (!(argument instanceof Iterable)) throw new IllegalArgumentException("Not an instance of Iterrable");
- @SuppressWarnings("unchecked")
- final Iterable actual = (Iterable) argument;
+ @Override
+ public boolean matches(Iterable actual) {
int i = 0;
for (KeyPair akp: actual) {
if (!eq(expected[i].getPublic(), akp.getPublic())) return false;
diff --git a/core/pom.xml b/core/pom.xml
index 9a582a5af472d4c477e527ca422b656f95d277df..cbbf3f30616771fbd074ee27f7f709e815ef576c 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -28,8 +28,8 @@ THE SOFTWARE.
org.jenkins-ci.main
- pom
- 2.73.4-SNAPSHOT
+ jenkins-parent
+ ${revision}${changelist}jenkins-core
@@ -39,11 +39,9 @@ THE SOFTWARE.
true
- 1.250
+ 1.2552.5.6.SEC03
- 2.4.11
-
- true
+ 2.4.12
@@ -95,6 +93,12 @@ THE SOFTWARE.
com.google.injectguice
+
+
+ com.google.guava
+ guava
+
+
@@ -105,7 +109,7 @@ THE SOFTWARE.
com.github.jnrjnr-posix
- 3.0.41
+ 3.0.45org.kohsuke
@@ -173,6 +177,12 @@ THE SOFTWARE.
teststest
+
+ org.hamcrest
+ hamcrest-library
+ 1.3
+ test
+ com.infradna.tool
@@ -197,17 +207,16 @@ THE SOFTWARE.
org.jenkins-ciannotation-indexer
- 1.12org.jenkins-cibytecode-compatibility-transformer
- 1.8
+ 2.0-beta-2org.jenkins-citask-reactor
- 1.4
+ 1.5org.jvnet.localizer
@@ -234,6 +243,20 @@ THE SOFTWARE.
+
+
+ xpp3
+ xpp3
+ 1.1.4c
+
+
+ net.sf.kxml
+ kxml2
+ 2.3.0
+ jfreejfreechart
@@ -256,7 +279,6 @@ THE SOFTWARE.
commons-langcommons-lang
- 2.6commons-digester
@@ -440,11 +462,6 @@ THE SOFTWARE.
spring-aop${spring.version}
-
- xpp3
- xpp3
- 1.1.4c
- junitjunit
@@ -462,13 +479,13 @@ THE SOFTWARE.
org.powermock
- powermock-api-mockito
+ powermock-api-mockito2test
- javax.servlet
- jstl
- 1.1.0
+ javax.servlet.jsp.jstl
+ javax.servlet.jsp.jstl-api
+ 1.2.1org.slf4j
@@ -496,7 +513,7 @@ THE SOFTWARE.
org.jvnet.winpwinp
- 1.25
+ 1.27org.jenkins-ci
@@ -516,7 +533,7 @@ THE SOFTWARE.
net.java.dev.jnajna
- 4.2.1
+ 4.5.2org.kohsuke
@@ -526,7 +543,7 @@ THE SOFTWARE.
org.kohsukelibpam4j
- 1.8
+ 1.11org.kohsuke
@@ -541,7 +558,7 @@ THE SOFTWARE.
net.java.sezpozsezpoz
- 1.12
+ 1.13org.kohsuke.jinterop
@@ -551,8 +568,7 @@ THE SOFTWARE.
org.kohsuke.metainf-servicesmetainf-services
- 1.4
- provided
+ 1.8true
@@ -566,10 +582,9 @@ THE SOFTWARE.
1.1
-
+ commons-codeccommons-codec
- 1.8
@@ -580,7 +595,6 @@ THE SOFTWARE.
com.google.code.findbugsannotations
- 3.0.0provided
@@ -602,6 +616,12 @@ THE SOFTWARE.
com.google.guavaguava
+
+
+ com.google.code.findbugs
+ jsr305
+
+ com.google.guava
@@ -621,6 +641,7 @@ THE SOFTWARE.
org.codehaus.mojobuild-helper-maven-plugin
+ 1.7add-source
@@ -785,25 +806,6 @@ THE SOFTWARE.
-
- org.codehaus.gmaven
- gmaven-plugin
-
-
-
-
- testCompile
-
-
-
-
-
- org.codehaus.groovy
- groovy-all
- ${groovy.version}
-
-
- org.codehaus.mojofindbugs-maven-plugin
@@ -878,41 +880,5 @@ THE SOFTWARE.
true
-
-
- cobertura
-
-
-
- org.codehaus.gmaven
- gmaven-plugin
-
-
-
-
- test
-
- execute
-
-
-
- ${project.basedir}/src/build-script
-
-
-
-
-
-
-
-
- maven-surefire-plugin
-
-
- true
-
-
-
-
-
diff --git a/core/src/build-script/Cobertura.groovy b/core/src/build-script/Cobertura.groovy
deleted file mode 100644
index 49b773b49ba17e79fa74975cf8a0b20d42153143..0000000000000000000000000000000000000000
--- a/core/src/build-script/Cobertura.groovy
+++ /dev/null
@@ -1,82 +0,0 @@
-import org.apache.maven.project.MavenProject;
-
-/**
- * Cobertura invoker.
- */
-public class Cobertura {
- private final MavenProject project;
- // maven helper
- private def maven;
- // ant builder
- private def ant;
- /**
- * Cobertura data file.
- */
- private final File ser;
-
- def Cobertura(project, maven, ant, ser) {
- this.project = maven.project;
- this.maven = maven;
- this.ant = ant;
- this.ser =ser;
-
- // define cobertura tasks
- ant.taskdef(resource:"tasks.properties")
- }
-
- // function that ensures that the given directory exists
- private String dir(String dir) {
- new File(project.basedir,dir).mkdirs();
- return dir;
- }
-
- /**
- * Instruments the given class dirs/jars by cobertura
- *
- * @param files
- * List of jar files and class dirs to instrument.
- */
- def instrument(files) {
- ant."cobertura-instrument"(todir:dir("target/cobertura-classes"),datafile:ser) {
- fileset(dir:"target/classes");
- files.each{ fileset(file:it) }
- }
- }
-
- def runTests() {
- ant.junit(fork:true, forkMode:"once", failureproperty:"failed", printsummary:true) {
- classpath {
- junitClasspath()
- }
- batchtest(todir:dir("target/surefire-reports")) {
- fileset(dir:"target/test-classes") {
- include(name:"**/*Test.class")
- }
- formatter(type:"xml")
- }
- sysproperty(key:"net.sourceforge.cobertura.datafile",value:ser)
- sysproperty(key:"hudson.ClassicPluginStrategy.useAntClassLoader",value:"true")
- }
- }
-
- def junitClasspath() {
- ant.pathelement(path: "target/cobertura-classes") // put the instrumented classes first
- ant.fileset(dir:"target/cobertura-classes",includes:"*.jar") // instrumented jar files
- ant.pathelement(path: maven.resolveArtifact("net.sourceforge.cobertura:cobertura:1.9")) // cobertura runtime
- project.getTestClasspathElements().each { ant.pathelement(path: it) } // the rest of the dependencies
- }
-
- def report(dirs) {
- maven.attachArtifact(ser,"ser","cobertura")
- ["html","xml"].each { format ->
- ant."cobertura-report"(format:format,datafile:ser,destdir:dir("target/cobertura-reports"),srcdir:"src/main/java") {
- dirs.each{ fileset(dir:it) }
- }
- }
- }
-
- def makeBuildFailIfTestFail() {
- if(ant.project.getProperty("failed")!=null && !Boolean.getBoolean("testFailureIgnore"))
- throw new Exception("Some unit tests failed");
- }
-}
diff --git a/core/src/build-script/readme.txt b/core/src/build-script/readme.txt
deleted file mode 100644
index 6c53a01026b7921e493e1c42f7463c0857c17c69..0000000000000000000000000000000000000000
--- a/core/src/build-script/readme.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Parts of the build scripts that are written in Groovy.
-
-IntelliJ needs Groovy files to be in its source directory to provide completion and such,
-so we need this to be in its own directory.
\ No newline at end of file
diff --git a/core/src/build-script/unitTest.groovy b/core/src/build-script/unitTest.groovy
deleted file mode 100644
index 653391aad7f15bbc6cdea2366f5f143bd4a0ae2b..0000000000000000000000000000000000000000
--- a/core/src/build-script/unitTest.groovy
+++ /dev/null
@@ -1,11 +0,0 @@
-// run unit tests
-
-ant.project.setBaseDir(project.basedir)
-ser=new File(project.basedir,"target/cobertura.ser"); // store cobertura data file in a module-specific location
-
-cob = new Cobertura(project,maven,ant,ser);
-
-cob.instrument([])
-cob.runTests()
-cob.report([])
-cob.makeBuildFailIfTestFail();
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties
index 7378e405c76f9e21a930a2e698b8baf8f024a484..510a7f5f861f964e339da3ac0755dab0a1779d10 100644
--- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties
+++ b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties
@@ -1,27 +1,6 @@
-# The MIT License
-#
-# Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Giulio D'Ambrosi
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-
-NewVersionAvailable=C''\u00E8 una nuova versione di Jenkins ({0}) disponibile per il download (changelog).
-Or\ Upgrade\ Automatically=Oppure aggiorna automaticamente
-UpgradeComplete=Aggiornamento a Jenkins {0} completato, in attesa di riavvio.
-UpgradeCompleteRestartNotSupported=Aggiornamento a Jenkins {0} completato, in attesa di riavvio.
-UpgradeProgress=Aggiornamento a Jenkins {0} in corso oppure fallito.
+NewVersionAvailable=Una nuova versione di Jenkins ({0}) disponibile per il download \
+ (log delle modifiche).
+UpgradeComplete=Aggiornamento a Jenkins {0} completato, in attesa del riavvio.
+UpgradeCompleteRestartNotSupported=Aggiornamento a Jenkins {0} completato, in attesa del riavvio.
+UpgradeProgress=Aggiornamento a Jenkins {0} in corso.
+UpgradeFailed=Aggiornamento a Jenkins {0} non riuscito: {1}.
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_zh_CN.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_zh_CN.properties
deleted file mode 100644
index 0ef79b530b974daaaed8ae9bc346ed86fb71a2bc..0000000000000000000000000000000000000000
--- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_zh_CN.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file is under the MIT License by authors
-
-NewVersionAvailable=Jenkins\u65B0\u7248\u672C ({0})\u53EF\u70B9\u51FB download (\u53D8\u66F4\u8BF4\u660E)\u4E0B\u8F7D\u3002
-Or\ Upgrade\ Automatically=\u6216 \u81EA\u52A8\u5347\u7EA7\u7248\u672C
-UpgradeComplete=Jenkins {0} \u7248\u672C\u5347\u7EA7\u5DF2\u5B8C\u6210,\u7B49\u5F85\u91CD\u542F\u4E2D.
-UpgradeCompleteRestartNotSupported=Jenkins {0} \u7248\u672C\u5347\u7EA7\u5DF2\u5B8C\u6210,\u7B49\u5F85\u91CD\u542F\u4E2D.
diff --git a/core/src/filter/resources/hudson/model/hudson-version.properties b/core/src/filter/resources/hudson/model/hudson-version.properties
index 9b9343d10d659357b7acccbcf34bc2aece573625..defbd48204e4784090fe0e17e28fe5bc395abf47 100644
--- a/core/src/filter/resources/hudson/model/hudson-version.properties
+++ b/core/src/filter/resources/hudson/model/hudson-version.properties
@@ -1 +1 @@
-version=${build.version}
\ No newline at end of file
+version=${project.version}
diff --git a/core/src/filter/resources/jenkins/model/jenkins-version.properties b/core/src/filter/resources/jenkins/model/jenkins-version.properties
index 9b9343d10d659357b7acccbcf34bc2aece573625..defbd48204e4784090fe0e17e28fe5bc395abf47 100644
--- a/core/src/filter/resources/jenkins/model/jenkins-version.properties
+++ b/core/src/filter/resources/jenkins/model/jenkins-version.properties
@@ -1 +1 @@
-version=${build.version}
\ No newline at end of file
+version=${project.version}
diff --git a/core/src/filter/resources/jenkins/slaves/remoting-info.properties b/core/src/filter/resources/jenkins/slaves/remoting-info.properties
new file mode 100644
index 0000000000000000000000000000000000000000..6ac78f4ad43e96e9f1277206b67049f098979a21
--- /dev/null
+++ b/core/src/filter/resources/jenkins/slaves/remoting-info.properties
@@ -0,0 +1,6 @@
+# Remoting version, which is embedded into the core
+# This version MAY differ from what is really classloaded (see the "Pluggable Core Components", JENKINS-41196)
+remoting.embedded.version=${remoting.version}
+
+# Minimum Remoting version on external agents which is supported by the core
+remoting.minimum.supported.version=${remoting.minimum.supported.version}
diff --git a/core/src/main/groovy/hudson/util/LoadMonitor.groovy b/core/src/main/groovy/hudson/util/LoadMonitor.groovy
deleted file mode 100644
index 97d701816199c011e67c6fcc39e539563442f225..0000000000000000000000000000000000000000
--- a/core/src/main/groovy/hudson/util/LoadMonitor.groovy
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package hudson.util
-
-import hudson.model.Computer
-import jenkins.util.Timer
-import jenkins.model.Jenkins
-import hudson.model.Label
-import hudson.model.Queue.BlockedItem
-import hudson.model.Queue.BuildableItem
-import hudson.model.Queue.WaitingItem
-import hudson.triggers.SafeTimerTask
-import java.text.DateFormat
-import java.util.concurrent.TimeUnit
-
-/**
- * Spits out the load information.
- *
- *
- * I'm using this code to design the auto scaling feature.
- * In future this might be useful data to expose to the UI.
- *
- * @author Kohsuke Kawaguchi
- */
-public class LoadMonitorImpl extends SafeTimerTask {
-
- private final File dataFile;
- private List labels;
-
- public LoadMonitorImpl(File dataFile) {
- this.dataFile = dataFile;
- labels = Jenkins.getInstance().labels*.name;
- printHeaders();
- Timer.get().scheduleAtFixedRate(this,0,10*1000, TimeUnit.MILLISECONDS);
- }
-
- private String quote(Object s) { "\"${s}\""; }
-
- protected void printHeaders() {
- def headers = ["# of executors","# of busy executors","BuildableItems in Q","BuildableItem avg wait time"];
- def data = ["timestamp"];
- data += headers;
- data += ["WaitingItems in Q","BlockedItems in Q"];
-
- for( String label : labels)
- data += headers.collect { "${it} (${label}}" }
-
- dataFile.append(data.collect({ quote(it) }).join(",")+"\n");
- }
-
- @Override
- protected void doRun() {
- def now = new Date();
- def data = [];
- data.add(quote(FORMATTER.format(now)));
-
- def h = Jenkins.getInstance();
-
- def items = h.queue.items;
- def filterByType = {Class type -> items.findAll { type.isInstance(it) } }
-
- def builder = {List cs, Closure itemFilter ->
- // number of total executor, number of busy executor
- data.add(cs.sum { it.isOffline() ? 0 : it.numExecutors });
- data.add(cs.sum {Computer c ->
- c.executors.findAll { !it.isIdle() }.size()
- });
-
- // queue statistics
- def is = filterByType(BuildableItem).findAll(itemFilter);
- data.add(is.size());
- data.add(is.sum {BuildableItem bi -> now.time - bi.buildableStartMilliseconds }?:0 / Math.max(1,is.size()) );
- };
-
-
- // for the whole thing
- builder(Arrays.asList(h.computers),{ it->true });
-
- data.add(filterByType(WaitingItem).size());
- data.add(filterByType(BlockedItem).size());
-
- // per label stats
- for (String label : labels) {
- Label l = h.getLabel(label)
- builder(l.nodes.collect { it.toComputer() }) { BuildableItem bi -> bi.task.assignedLabel==l };
- }
-
- dataFile.append(data.join(",")+"\n");
- }
-
- private static final DateFormat FORMATTER = DateFormat.getDateTimeInstance();
-}
-
-new LoadMonitorImpl(new File("/files/hudson/load.txt"));
-
diff --git a/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy b/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy
deleted file mode 100644
index 08cb1c23a3da552cc531cf3b655b0a915b43aee6..0000000000000000000000000000000000000000
--- a/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy
+++ /dev/null
@@ -1,47 +0,0 @@
-package jenkins.util.groovy
-
-import lib.FormTagLib
-import lib.LayoutTagLib
-import org.kohsuke.stapler.jelly.groovy.JellyBuilder
-import org.kohsuke.stapler.jelly.groovy.Namespace
-import lib.JenkinsTagLib
-
-/**
- * Base class for utility classes for Groovy view scripts
- *
- * Usage from script of a subclass, say ViewHelper:
- *
- * new ViewHelper(delegate).method();
- *
- * see ModularizeViewScript in ui-samples for an example how to use this class.
- *
- * @author Stefan Wolf (wolfs)
- */
-abstract class AbstractGroovyViewModule {
- JellyBuilder builder
- FormTagLib f
- LayoutTagLib l
- JenkinsTagLib t
- Namespace st
-
- public AbstractGroovyViewModule(JellyBuilder b) {
- builder = b
- f= builder.namespace(FormTagLib)
- l=builder.namespace(LayoutTagLib)
- t=builder.namespace(JenkinsTagLib)
- st=builder.namespace("jelly:stapler")
-
- }
-
- def methodMissing(String name, args) {
- builder.invokeMethod(name,args)
- }
-
- def propertyMissing(String name) {
- builder.getProperty(name)
- }
-
- def propertyMissing(String name, value) {
- builder.setProperty(name, value)
- }
-}
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index 5981ade06904c23de2c75603ec328476db760fe3..5d173c3839022d162265f7b2a2779c436e30791a 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -23,10 +23,15 @@
*/
package hudson;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jenkins.util.AntWithFindResourceClassLoader;
import jenkins.util.SystemProperties;
import com.google.common.collect.Lists;
@@ -58,25 +63,24 @@ import org.apache.tools.zip.ZipOutputStream;
import java.io.Closeable;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
-import java.lang.reflect.Field;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.Vector;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.jenkinsci.bytecode.Transformer;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -109,11 +113,20 @@ public class ClassicPluginStrategy implements PluginStrategy {
@Override public String getShortName(File archive) throws IOException {
Manifest manifest;
+ if (!archive.exists()) {
+ throw new FileNotFoundException("Failed to load " + archive + ". The file does not exist");
+ } else if (!archive.isFile()) {
+ throw new FileNotFoundException("Failed to load " + archive + ". It is not a file");
+ }
+
if (isLinked(archive)) {
manifest = loadLinkedManifest(archive);
} else {
try (JarFile jf = new JarFile(archive, false)) {
manifest = jf.getManifest();
+ } catch (IOException ex) {
+ // Mention file name in the exception
+ throw new IOException("Failed to load " + archive, ex);
}
}
return PluginWrapper.computeShortName(manifest, archive.getName());
@@ -268,7 +281,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
if (detached.shortName.equals(pluginName)) {
continue;
}
- if (BREAK_CYCLES.contains(pluginName + '/' + detached.shortName)) {
+ if (BREAK_CYCLES.contains(pluginName + ' ' + detached.shortName)) {
LOGGER.log(Level.FINE, "skipping implicit dependency {0} → {1}", new Object[] {pluginName, detached.shortName});
continue;
}
@@ -408,35 +421,37 @@ public class ClassicPluginStrategy implements PluginStrategy {
public VersionNumber getRequiredVersion() {
return new VersionNumber(requiredVersion);
}
+
+ @Override
+ public String toString() {
+ return shortName + " " + splitWhen.toString().replace(".*", "") + " " + requiredVersion;
+ }
}
- private static final List DETACHED_LIST = Collections.unmodifiableList(Arrays.asList(
- new DetachedPlugin("maven-plugin", "1.296", "1.296"),
- new DetachedPlugin("subversion", "1.310", "1.0"),
- new DetachedPlugin("cvs", "1.340", "0.1"),
- new DetachedPlugin("ant", "1.430.*", "1.0"),
- new DetachedPlugin("javadoc", "1.430.*", "1.0"),
- new DetachedPlugin("external-monitor-job", "1.467.*", "1.0"),
- new DetachedPlugin("ldap", "1.467.*", "1.0"),
- new DetachedPlugin("pam-auth", "1.467.*", "1.0"),
- new DetachedPlugin("mailer", "1.493.*", "1.2"),
- new DetachedPlugin("matrix-auth", "1.535.*", "1.0.2"),
- new DetachedPlugin("windows-slaves", "1.547.*", "1.0"),
- new DetachedPlugin("antisamy-markup-formatter", "1.553.*", "1.0"),
- new DetachedPlugin("matrix-project", "1.561.*", "1.0"),
- new DetachedPlugin("junit", "1.577.*", "1.0"),
- new DetachedPlugin("bouncycastle-api", "2.16.*", "2.16.0")
- ));
+ /** Record of which plugins which removed from core and when. */
+ private static final List DETACHED_LIST;
/** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */
- private static final Set BREAK_CYCLES = new HashSet(Arrays.asList(
- "script-security/matrix-auth",
- "script-security/windows-slaves",
- "script-security/antisamy-markup-formatter",
- "script-security/matrix-project",
- "credentials/matrix-auth",
- "credentials/windows-slaves"
- ));
+ private static final Set BREAK_CYCLES;
+
+ static {
+ try (InputStream is = ClassicPluginStrategy.class.getResourceAsStream("/jenkins/split-plugins.txt")) {
+ DETACHED_LIST = ImmutableList.copyOf(configLines(is).map(line -> {
+ String[] pieces = line.split(" ");
+ return new DetachedPlugin(pieces[0], pieces[1] + ".*", pieces[2]);
+ }).collect(Collectors.toList()));
+ } catch (IOException x) {
+ throw new ExceptionInInitializerError(x);
+ }
+ try (InputStream is = ClassicPluginStrategy.class.getResourceAsStream("/jenkins/split-plugin-cycles.txt")) {
+ BREAK_CYCLES = ImmutableSet.copyOf(configLines(is).collect(Collectors.toSet()));
+ } catch (IOException x) {
+ throw new ExceptionInInitializerError(x);
+ }
+ }
+ private static Stream configLines(InputStream is) throws IOException {
+ return org.apache.commons.io.IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*"));
+ }
/**
* Computes the classloader that takes the class masking into account.
@@ -764,19 +779,20 @@ public class ClassicPluginStrategy implements PluginStrategy {
Class> c = ClassLoaderReflectionToolkit._findLoadedClass(pw.classLoader, name);
if (c!=null) return c;
return ClassLoaderReflectionToolkit._findClass(pw.classLoader, name);
- } catch (ClassNotFoundException e) {
+ } catch (ClassNotFoundException ignored) {
//not found. try next
}
}
} else {
for (Dependency dep : dependencies) {
PluginWrapper p = pluginManager.getPlugin(dep.shortName);
- if(p!=null)
+ if(p!=null) {
try {
return p.classLoader.loadClass(name);
- } catch (ClassNotFoundException _) {
- // try next
+ } catch (ClassNotFoundException ignored) {
+ // OK, try next
}
+ }
}
}
@@ -784,6 +800,8 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
@Override
+ @SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS",
+ justification = "Should not produce network overheads since the URL is local. JENKINS-53793 is a follow-up")
protected Enumeration findResources(String name) throws IOException {
HashSet result = new HashSet();
diff --git a/core/src/main/java/hudson/CopyOnWrite.java b/core/src/main/java/hudson/CopyOnWrite.java
index efe21357c4405e122651a284c0cb4c30157fce62..891cbf04bb9ffef6d6f657d05eb4708d94fa0f5e 100644
--- a/core/src/main/java/hudson/CopyOnWrite.java
+++ b/core/src/main/java/hudson/CopyOnWrite.java
@@ -43,7 +43,7 @@ import java.lang.annotation.Target;
*
*
* The field marked with this annotation usually needs to be marked as
- * volatile.
+ * {@code volatile}.
*
* @author Kohsuke Kawaguchi
*/
diff --git a/core/src/main/java/hudson/DescriptorExtensionList.java b/core/src/main/java/hudson/DescriptorExtensionList.java
index 16d96ae441b0ff5a7cc4a3e59e8398db0cc595a4..dd334db858be47f551cf25a897d37b11d095fae5 100644
--- a/core/src/main/java/hudson/DescriptorExtensionList.java
+++ b/core/src/main/java/hudson/DescriptorExtensionList.java
@@ -31,7 +31,6 @@ import jenkins.model.Jenkins;
import hudson.model.ViewDescriptor;
import hudson.model.Descriptor.FormException;
import hudson.util.AdaptedIterator;
-import hudson.util.Memoizer;
import hudson.util.Iterators.FlattenIterator;
import hudson.slaves.NodeDescriptor;
import hudson.tasks.Publisher;
@@ -41,6 +40,8 @@ import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -213,14 +214,12 @@ public class DescriptorExtensionList, D extends Descrip
/**
* Stores manually registered Descriptor instances. Keyed by the {@link Describable} type.
*/
- private static final Memoizer>> legacyDescriptors = new Memoizer>>() {
- public CopyOnWriteArrayList compute(Class key) {
- return new CopyOnWriteArrayList();
- }
- };
+ @SuppressWarnings("rawtypes")
+ private static final Map>> legacyDescriptors = new ConcurrentHashMap<>();
+ @SuppressWarnings({"unchecked", "rawtypes"})
private static > CopyOnWriteArrayList>> getLegacyDescriptors(Class type) {
- return (CopyOnWriteArrayList)legacyDescriptors.get(type);
+ return legacyDescriptors.computeIfAbsent(type, key -> new CopyOnWriteArrayList());
}
/**
diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java
index 1849ecbf66aebaf6ca6a4df5ce197b5b825c8b36..cd553f3aba01ea325c58912b990d88aab9cb5e66 100644
--- a/core/src/main/java/hudson/EnvVars.java
+++ b/core/src/main/java/hudson/EnvVars.java
@@ -44,6 +44,7 @@ import java.util.TreeSet;
import java.util.UUID;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
+import javax.annotation.CheckForNull;
/**
* Environment variables.
@@ -54,7 +55,7 @@ import javax.annotation.Nonnull;
* but case insensitive way (that is, cmd.exe can get both FOO and foo as environment variables
* when it's launched, and the "set" command will display it accordingly, but "echo %foo%" results in
* echoing the value of "FOO", not "foo" — this is presumably caused by the behavior of the underlying
- * Win32 API GetEnvironmentVariable acting in case insensitive way.) Windows users are also
+ * Win32 API {@code GetEnvironmentVariable} acting in case insensitive way.) Windows users are also
* used to write environment variable case-insensitively (like %Path% vs %PATH%), and you can see many
* documents on the web that claims Windows environment variables are case insensitive.
*
@@ -65,10 +66,10 @@ import javax.annotation.Nonnull;
*
* In Jenkins, often we need to build up "environment variable overrides"
* on master, then to execute the process on agents. This causes a problem
- * when working with variables like PATH. So to make this work,
- * we introduce a special convention PATH+FOO — all entries
- * that starts with PATH+ are merged and prepended to the inherited
- * PATH variable, on the process where a new process is executed.
+ * when working with variables like {@code PATH}. So to make this work,
+ * we introduce a special convention {@code PATH+FOO} — all entries
+ * that starts with {@code PATH+} are merged and prepended to the inherited
+ * {@code PATH} variable, on the process where a new process is executed.
*
* @author Kohsuke Kawaguchi
*/
@@ -84,7 +85,24 @@ public class EnvVars extends TreeMap {
* So this property remembers that information.
*/
private Platform platform;
+
+ /**
+ * Gets the platform for which these env vars targeted.
+ * @since TODO
+ * @return The platform.
+ */
+ public @CheckForNull Platform getPlatform() {
+ return platform;
+ }
+ /**
+ * Sets the platform for which these env vars target.
+ * @since TODO
+ * @param platform the platform to set.
+ */
+ public void setPlatform(@Nonnull Platform platform) {
+ this.platform = platform;
+ }
public EnvVars() {
super(CaseInsensitiveComparator.INSTANCE);
}
@@ -107,7 +125,7 @@ public class EnvVars extends TreeMap {
}
/**
- * Builds an environment variables from an array of the form "key","value","key","value"...
+ * Builds an environment variables from an array of the form {@code "key","value","key","value"...}
*/
public EnvVars(String... keyValuePairs) {
this();
@@ -121,7 +139,7 @@ public class EnvVars extends TreeMap {
* Overrides the current entry by the given entry.
*
*
* If you access this field from agents, then this is the environment
- * variable of the agent agent.
+ * variable of the agent.
*/
public static final Map masterEnvVars = initMaster();
diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java
index 6208f6cec8322a0145f2fc4436519000d07e0ce6..1ff0638c10d6b578927ea90757d5acb8bdbeff6b 100644
--- a/core/src/main/java/hudson/ExtensionFinder.java
+++ b/core/src/main/java/hudson/ExtensionFinder.java
@@ -23,6 +23,7 @@
*/
package hudson;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -35,11 +36,13 @@ import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Scopes;
+import com.google.inject.matcher.Matchers;
import com.google.inject.name.Names;
-import com.google.common.collect.ImmutableList;
+import com.google.inject.spi.ProvisionListener;
import hudson.init.InitMilestone;
import hudson.model.Descriptor;
import hudson.model.Hudson;
+import hudson.util.ReflectionUtils;
import jenkins.ExtensionComponentSet;
import jenkins.ExtensionFilter;
import jenkins.ExtensionRefreshException;
@@ -49,20 +52,25 @@ import net.java.sezpoz.Index;
import net.java.sezpoz.IndexItem;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.springframework.util.ClassUtils;
+import javax.annotation.PostConstruct;
import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.logging.Logger;
+import java.util.Set;
import java.util.logging.Level;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
+import java.util.logging.Logger;
/**
* Discovers the implementations of an extension point.
@@ -254,12 +262,9 @@ public abstract class ExtensionFinder implements ExtensionPoint {
private Map,GuiceExtensionAnnotation>> extensionAnnotations = Maps.newHashMap();
public GuiceFinder() {
- for (ExtensionComponent ec : moduleFinder.find(GuiceExtensionAnnotation.class, Hudson.getInstance())) {
- GuiceExtensionAnnotation gea = ec.getInstance();
- extensionAnnotations.put(gea.annotationType,gea);
- }
+ refreshExtensionAnnotations();
- sezpozIndex = loadSezpozIndices(Jenkins.getInstance().getPluginManager().uberClassLoader);
+ SezpozModule extensions = new SezpozModule(loadSezpozIndices(Jenkins.getInstance().getPluginManager().uberClassLoader));
List modules = new ArrayList<>();
modules.add(new AbstractModule() {
@@ -270,7 +275,7 @@ public abstract class ExtensionFinder implements ExtensionPoint {
bind(PluginManager.class).toInstance(j.getPluginManager());
}
});
- modules.add(new SezpozModule(sezpozIndex));
+ modules.add(extensions);
for (ExtensionComponent ec : moduleFinder.find(Module.class, Hudson.getInstance())) {
modules.add(ec.getInstance());
@@ -278,6 +283,7 @@ public abstract class ExtensionFinder implements ExtensionPoint {
try {
container = Guice.createInjector(modules);
+ sezpozIndex = extensions.getLoadedIndex();
} catch (Throwable e) {
LOGGER.log(Level.SEVERE, "Failed to create Guice container from all the plugins",e);
// failing to load all bindings are disastrous, so recover by creating minimum that works
@@ -293,6 +299,13 @@ public abstract class ExtensionFinder implements ExtensionPoint {
});
}
+ private void refreshExtensionAnnotations() {
+ for (ExtensionComponent ec : moduleFinder.find(GuiceExtensionAnnotation.class, Hudson.getInstance())) {
+ GuiceExtensionAnnotation gea = ec.getInstance();
+ extensionAnnotations.put(gea.annotationType,gea);
+ }
+ }
+
private ImmutableList> loadSezpozIndices(ClassLoader classLoader) {
List> indices = Lists.newArrayList();
for (GuiceExtensionAnnotation> gea : extensionAnnotations.values()) {
@@ -315,17 +328,17 @@ public abstract class ExtensionFinder implements ExtensionPoint {
*/
@Override
public synchronized ExtensionComponentSet refresh() throws ExtensionRefreshException {
+ refreshExtensionAnnotations();
// figure out newly discovered sezpoz components
List> delta = Lists.newArrayList();
for (Class extends Annotation> annotationType : extensionAnnotations.keySet()) {
delta.addAll(Sezpoz.listDelta(annotationType,sezpozIndex));
}
- List> l = Lists.newArrayList(sezpozIndex);
- l.addAll(delta);
- sezpozIndex = l;
+
+ SezpozModule deltaExtensions = new SezpozModule(delta);
List modules = new ArrayList<>();
- modules.add(new SezpozModule(delta));
+ modules.add(deltaExtensions);
for (ExtensionComponent ec : moduleFinder.refresh().find(Module.class)) {
modules.add(ec.getInstance());
}
@@ -333,6 +346,9 @@ public abstract class ExtensionFinder implements ExtensionPoint {
try {
final Injector child = container.createChildInjector(modules);
container = child;
+ List> l = Lists.newArrayList(sezpozIndex);
+ l.addAll(deltaExtensions.getLoadedIndex());
+ sezpozIndex = l;
return new ExtensionComponentSet() {
@Override
@@ -446,11 +462,13 @@ public abstract class ExtensionFinder implements ExtensionPoint {
* Instead of using SezPoz to instantiate, we'll instantiate them by using Guice,
* so that we can take advantage of dependency injection.
*/
- private class SezpozModule extends AbstractModule {
+ private class SezpozModule extends AbstractModule implements ProvisionListener {
private final List> index;
+ private final List> loadedIndex;
public SezpozModule(List> index) {
this.index = index;
+ this.loadedIndex = new ArrayList<>();
}
/**
@@ -493,6 +511,9 @@ public abstract class ExtensionFinder implements ExtensionPoint {
@SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"})
@Override
protected void configure() {
+
+ bindListener(Matchers.any(), this);
+
for (final IndexItem,Object> item : index) {
boolean optional = isOptional(item.annotation());
try {
@@ -527,6 +548,7 @@ public abstract class ExtensionFinder implements ExtensionPoint {
}
}).in(scope);
}
+ loadedIndex.add(item);
} catch (Exception|LinkageError e) {
// sometimes the instantiation fails in an indirect classloading failure,
// which results in a LinkageError
@@ -535,9 +557,68 @@ public abstract class ExtensionFinder implements ExtensionPoint {
}
}
}
+
+ public List> getLoadedIndex() {
+ return Collections.unmodifiableList(loadedIndex);
+ }
+
+ @Override
+ public void onProvision(ProvisionInvocation provision) {
+ final T instance = provision.provision();
+ if (instance == null) return;
+ List methods = new LinkedList<>();
+ Class c = instance.getClass();
+
+ // find PostConstruct methods in class hierarchy, the one from parent class being first in list
+ // so that we invoke them before derived class one. This isn't specified in JSR-250 but implemented
+ // this way in Spring and what most developers would expect to happen.
+
+ final Set interfaces = ClassUtils.getAllInterfacesAsSet(instance);
+
+ while (c != Object.class) {
+ Arrays.stream(c.getDeclaredMethods())
+ .map(m -> getMethodAndInterfaceDeclarations(m, interfaces))
+ .flatMap(Collection::stream)
+ .filter(m -> m.getAnnotation(PostConstruct.class) != null)
+ .findFirst()
+ .ifPresent(method -> methods.add(0, method));
+ c = c.getSuperclass();
+ }
+
+ for (Method postConstruct : methods) {
+ try {
+ postConstruct.setAccessible(true);
+ postConstruct.invoke(instance);
+ } catch (final Exception e) {
+ throw new RuntimeException(String.format("@PostConstruct %s", postConstruct), e);
+ }
+ }
+ }
}
}
+ /**
+ * Returns initial {@link Method} as well as all matching ones found in interfaces.
+ * This allows to introspect metadata for a method which is both declared in parent class and in implemented
+ * interface(s). interfaces typically is obtained by {@link ClassUtils#getAllInterfacesAsSet}
+ */
+ Collection getMethodAndInterfaceDeclarations(Method method, Collection interfaces) {
+ final List methods = new ArrayList<>();
+ methods.add(method);
+
+ // we search for matching method by iteration and comparison vs getMethod to avoid repeated NoSuchMethodException
+ // being thrown, while interface typically only define a few set of methods to check.
+ interfaces.stream()
+ .map(Class::getMethods)
+ .flatMap(Arrays::stream)
+ .filter(m -> m.getName().equals(method.getName()) && Arrays.equals(m.getParameterTypes(), method.getParameterTypes()))
+ .findFirst()
+ .ifPresent(methods::add);
+
+ return methods;
+ }
+
+
/**
* The bootstrap implementation that looks for the {@link Extension} marker.
*
diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java
index f2fcab81d403e63588322b73ceacae33dce66617..566e3de404911db2fbc418bab933a4bdd8261c79 100644
--- a/core/src/main/java/hudson/ExtensionList.java
+++ b/core/src/main/java/hudson/ExtensionList.java
@@ -30,7 +30,6 @@ import jenkins.ExtensionComponentSet;
import jenkins.model.Jenkins;
import hudson.util.AdaptedIterator;
import hudson.util.DescriptorList;
-import hudson.util.Memoizer;
import hudson.util.Iterators;
import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson;
@@ -40,7 +39,9 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Vector;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -144,15 +145,29 @@ public class ExtensionList extends AbstractList implements OnMaster {
* Looks for the extension instance of the given type (subclasses excluded),
* or return null.
*/
- public @CheckForNull U get(Class type) {
+ public @CheckForNull U get(@Nonnull Class type) {
for (T ext : this)
if(ext.getClass()==type)
return type.cast(ext);
return null;
}
+ /**
+ * Looks for the extension instance of the given type (subclasses excluded),
+ * or throws an IllegalStateException.
+ *
+ * Meant to simplify call inside @Extension annotated class to retrieve their own instance.
+ */
+ public @Nonnull U getInstance(@Nonnull Class type) throws IllegalStateException {
+ for (T ext : this)
+ if(ext.getClass()==type)
+ return type.cast(ext);
+
+ throw new IllegalStateException("The class " + type.getName() + " was not found, potentially not yet loaded");
+ }
+
@Override
- public Iterator iterator() {
+ public @Nonnull Iterator iterator() {
// we need to intercept mutation, so for now don't allow Iterator.remove
return new AdaptedIterator,T>(Iterators.readOnly(ensureLoaded().iterator())) {
protected T adapt(ExtensionComponent item) {
@@ -395,11 +410,12 @@ public class ExtensionList extends AbstractList implements OnMaster {
return create((Jenkins)hudson,type);
}
+ @SuppressWarnings({"unchecked", "rawtypes"})
public static ExtensionList create(Jenkins jenkins, Class type) {
if(type.getAnnotation(LegacyInstancesAreScopedToHudson.class)!=null)
return new ExtensionList(jenkins,type);
else {
- return new ExtensionList(jenkins,type,staticLegacyInstances.get(type));
+ return new ExtensionList(jenkins, type, staticLegacyInstances.computeIfAbsent(type, key -> new CopyOnWriteArrayList()));
}
}
@@ -418,13 +434,29 @@ public class ExtensionList extends AbstractList implements OnMaster {
}
/**
- * Places to store static-scope legacy instances.
+ * Convenience method allowing lookup of the only instance of a given type.
+ * Equivalent to {@code ExtensionList.lookup(Class).get(Class)} if there is one instance,
+ * and throws an {@code IllegalStateException} otherwise.
+ *
+ * @param type The type to look up.
+ * @return the singleton instance of the given type in its list.
+ * @throws IllegalStateException if there are no instances, or more than one
+ *
+ * @since 2.87
*/
- private static final Memoizer staticLegacyInstances = new Memoizer() {
- public CopyOnWriteArrayList compute(Class key) {
- return new CopyOnWriteArrayList();
+ public static @Nonnull U lookupSingleton(Class type) {
+ ExtensionList all = lookup(type);
+ if (all.size() != 1) {
+ throw new IllegalStateException("Expected 1 instance of " + type.getName() + " but got " + all.size());
}
- };
+ return all.get(0);
+ }
+
+ /**
+ * Places to store static-scope legacy instances.
+ */
+ @SuppressWarnings("rawtypes")
+ private static final Map staticLegacyInstances = new ConcurrentHashMap<>();
/**
* Exposed for the test harness to clear all legacy extension instances.
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index cd3fa60451c232c8ce42d1536a16430a08206075..9452064e8105e124c0e62328f3310c03765b4ff7 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -58,6 +58,9 @@ import hudson.util.IOUtils;
import hudson.util.NamingThreadFactory;
import hudson.util.io.Archiver;
import hudson.util.io.ArchiverFactory;
+
+import static java.util.logging.Level.FINE;
+
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
@@ -77,11 +80,19 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.LinkOption;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.EnumSet;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
@@ -106,12 +117,12 @@ import jenkins.SoloFilePathFilter;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import jenkins.util.ContextResettingExecutorService;
-import jenkins.util.SystemProperties;
import jenkins.util.VirtualFile;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.input.CountingInputStream;
+import org.apache.commons.lang.StringUtils;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
@@ -125,9 +136,12 @@ import org.kohsuke.stapler.Stapler;
import static hudson.FilePath.TarCompression.GZIP;
import static hudson.Util.deleteFile;
+import static hudson.Util.fileToPath;
import static hudson.Util.fixEmpty;
import static hudson.Util.isSymlink;
+
import java.util.Collections;
+import org.apache.tools.ant.BuildException;
/**
* {@link File} like object with remoting support.
@@ -214,9 +228,14 @@ public final class FilePath implements Serializable {
* This is used to determine whether we are running on the master or the agent.
*/
private transient VirtualChannel channel;
-
- // since the platform of the agent might be different, can't use java.io.File
- private final String remote;
+
+ /**
+ * Represent the path to the file in the master or the agent
+ * Since the platform of the agent might be different, can't use java.io.File
+ *
+ * The field could not be final since it's modified in {@link #readResolve()}
+ */
+ private /*final*/ String remote;
/**
* If this {@link FilePath} is deserialized to handle file access request from a remote computer,
@@ -264,6 +283,11 @@ public final class FilePath implements Serializable {
this.remote = normalize(resolvePathIfRelative(base, rel));
}
+ private Object readResolve() {
+ this.remote = normalize(this.remote);
+ return this;
+ }
+
private String resolvePathIfRelative(@Nonnull FilePath base, @Nonnull String rel) {
if(isAbsolute(rel)) return rel;
if(base.isUnix()) {
@@ -291,7 +315,8 @@ public final class FilePath implements Serializable {
* {@link File#getParent()} etc cannot handle ".." and "." in the path component very well,
* so remove them.
*/
- private static String normalize(@Nonnull String path) {
+ @Restricted(NoExternalUse.class)
+ public static String normalize(@Nonnull String path) {
StringBuilder buf = new StringBuilder();
// Check for prefix designating absolute path
Matcher m = ABSOLUTE_PREFIX_PATTERN.matcher(path);
@@ -455,7 +480,18 @@ public final class FilePath implements Serializable {
*/
public int archive(final ArchiverFactory factory, OutputStream os, final DirScanner scanner) throws IOException, InterruptedException {
final OutputStream out = (channel!=null)?new RemoteOutputStream(os):os;
- return act(new SecureFileCallable() {
+ return act(new Archive(factory, out, scanner));
+ }
+ private class Archive extends SecureFileCallable {
+ private final ArchiverFactory factory;
+ private final OutputStream out;
+ private final DirScanner scanner;
+ Archive(ArchiverFactory factory, OutputStream out, DirScanner scanner) {
+ this.factory = factory;
+ this.out = out;
+ this.scanner = scanner;
+ }
+ @Override
public Integer invoke(File f, VirtualChannel channel) throws IOException {
Archiver a = factory.create(out);
try {
@@ -467,7 +503,6 @@ public final class FilePath implements Serializable {
}
private static final long serialVersionUID = 1L;
- });
}
public int archive(final ArchiverFactory factory, OutputStream os, final FileFilter filter) throws IOException, InterruptedException {
@@ -490,27 +525,32 @@ public final class FilePath implements Serializable {
// TODO: post release, re-unite two branches by introducing FileStreamCallable that resolves InputStream
if (this.channel!=target.channel) {// local -> remote or remote->local
final RemoteInputStream in = new RemoteInputStream(read(), Flag.GREEDY);
- target.act(new SecureFileCallable() {
- public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
- unzip(dir, in);
- return null;
- }
-
- private static final long serialVersionUID = 1L;
- });
+ target.act(new UnzipRemote(in));
} else {// local -> local or remote->remote
- target.act(new SecureFileCallable() {
-
- public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
- assert !FilePath.this.isRemote(); // this.channel==target.channel above
- unzip(dir, reading(new File(FilePath.this.getRemote()))); // shortcut to local file
- return null;
- }
-
- private static final long serialVersionUID = 1L;
- });
+ target.act(new UnzipLocal());
}
}
+ private class UnzipRemote extends SecureFileCallable {
+ private final RemoteInputStream in;
+ UnzipRemote(RemoteInputStream in) {
+ this.in = in;
+ }
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
+ unzip(dir, in);
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
+ }
+ private class UnzipLocal extends SecureFileCallable {
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
+ assert !FilePath.this.isRemote(); // this.channel==target.channel above
+ unzip(dir, reading(new File(FilePath.this.getRemote()))); // shortcut to local file
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
+ }
/**
* When this {@link FilePath} represents a tar file, extracts that tar file.
@@ -526,24 +566,37 @@ public final class FilePath implements Serializable {
// TODO: post release, re-unite two branches by introducing FileStreamCallable that resolves InputStream
if (this.channel!=target.channel) {// local -> remote or remote->local
final RemoteInputStream in = new RemoteInputStream(read(), Flag.GREEDY);
- target.act(new SecureFileCallable() {
- public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
- readFromTar(FilePath.this.getName(),dir,compression.extract(in));
- return null;
- }
-
- private static final long serialVersionUID = 1L;
- });
+ target.act(new UntarRemote(compression, in));
} else {// local -> local or remote->remote
- target.act(new SecureFileCallable() {
- public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
- readFromTar(FilePath.this.getName(),dir,compression.extract(FilePath.this.read()));
- return null;
- }
- private static final long serialVersionUID = 1L;
- });
+ target.act(new UntarLocal(compression));
}
}
+ private class UntarRemote extends SecureFileCallable {
+ private final TarCompression compression;
+ private final RemoteInputStream in;
+ UntarRemote(TarCompression compression, RemoteInputStream in) {
+ this.compression = compression;
+ this.in = in;
+ }
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
+ readFromTar(FilePath.this.getName(), dir, compression.extract(in));
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
+ }
+ private class UntarLocal extends SecureFileCallable {
+ private final TarCompression compression;
+ UntarLocal(TarCompression compression) {
+ this.compression = compression;
+ }
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
+ readFromTar(FilePath.this.getName(), dir, compression.extract(FilePath.this.read()));
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
+ }
/**
* Reads the given InputStream as a zip file and extracts it into this directory.
@@ -555,13 +608,19 @@ public final class FilePath implements Serializable {
*/
public void unzipFrom(InputStream _in) throws IOException, InterruptedException {
final InputStream in = new RemoteInputStream(_in, Flag.GREEDY);
- act(new SecureFileCallable() {
- public Void invoke(File dir, VirtualChannel channel) throws IOException {
- unzip(dir, in);
- return null;
- }
- private static final long serialVersionUID = 1L;
- });
+ act(new UnzipFrom(in));
+ }
+ private class UnzipFrom extends SecureFileCallable {
+ private final InputStream in;
+ UnzipFrom(InputStream in) {
+ this.in = in;
+ }
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException {
+ unzip(dir, in);
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
}
private void unzip(File dir, InputStream in) throws IOException {
@@ -586,6 +645,10 @@ public final class FilePath implements Serializable {
while (entries.hasMoreElements()) {
ZipEntry e = entries.nextElement();
File f = new File(dir, e.getName());
+ if (!f.toPath().normalize().startsWith(dir.toPath())) {
+ throw new IOException(
+ "Zip " + zipFile.getPath() + " contains illegal file name that breaks out of the target directory: " + e.getName());
+ }
if (e.isDirectory()) {
mkdirs(f);
} else {
@@ -616,12 +679,13 @@ public final class FilePath implements Serializable {
* Absolutizes this {@link FilePath} and returns the new one.
*/
public FilePath absolutize() throws IOException, InterruptedException {
- return new FilePath(channel,act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File f, VirtualChannel channel) throws IOException {
- return f.getAbsolutePath();
- }
- }));
+ return new FilePath(channel, act(new Absolutize()));
+ }
+ private static class Absolutize extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ public String invoke(File f, VirtualChannel channel) throws IOException {
+ return f.getAbsolutePath();
+ }
}
/**
@@ -634,14 +698,22 @@ public final class FilePath implements Serializable {
* @since 1.456
*/
public void symlinkTo(final String target, final TaskListener listener) throws IOException, InterruptedException {
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- symlinking(f);
- Util.createSymlink(f.getParentFile(),target,f.getName(),listener);
- return null;
- }
- });
+ act(new SymlinkTo(target, listener));
+ }
+ private class SymlinkTo extends SecureFileCallable {
+ private final String target;
+ private final TaskListener listener;
+ SymlinkTo(String target, TaskListener listener) {
+ this.target = target;
+ this.listener = listener;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ symlinking(f);
+ Util.createSymlink(f.getParentFile(), target, f.getName(), listener);
+ return null;
+ }
}
/**
@@ -652,12 +724,14 @@ public final class FilePath implements Serializable {
* @since 1.456
*/
public String readLink() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- return Util.resolveSymlink(reading(f));
- }
- });
+ return act(new ReadLink());
+ }
+ private class ReadLink extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ return Util.resolveSymlink(reading(f));
+ }
}
@Override
@@ -721,17 +795,25 @@ public final class FilePath implements Serializable {
public void untarFrom(InputStream _in, final TarCompression compression) throws IOException, InterruptedException {
try {
final InputStream in = new RemoteInputStream(_in, Flag.GREEDY);
- act(new SecureFileCallable() {
- public Void invoke(File dir, VirtualChannel channel) throws IOException {
- readFromTar("input stream",dir, compression.extract(in));
- return null;
- }
- private static final long serialVersionUID = 1L;
- });
+ act(new UntarFrom(compression, in));
} finally {
_in.close();
}
}
+ private class UntarFrom extends SecureFileCallable {
+ private final TarCompression compression;
+ private final InputStream in;
+ UntarFrom(TarCompression compression, InputStream in) {
+ this.compression = compression;
+ this.in = in;
+ }
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException {
+ readFromTar("input stream",dir, compression.extract(in));
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
+ }
/**
* Given a tgz/zip file, extracts it to the given target directory, if necessary.
@@ -881,9 +963,10 @@ public final class FilePath implements Serializable {
}
/**
- * Reads the URL on the current VM, and writes all the data to this {@link FilePath}
- * (this is different from resolving URL remotely.)
- *
+ * Reads the URL on the current VM, and streams the data to this file using the Remoting channel.
+ *
This is different from resolving URL remotely.
+ * If you instead wished to open an HTTP(S) URL on the remote side,
+ * prefer {@code RobustHTTPClient.copyFromRemotely}.
* @since 1.293
*/
public void copyFrom(URL url) throws IOException, InterruptedException {
@@ -987,11 +1070,6 @@ public final class FilePath implements Serializable {
return channel.call(wrapper);
} catch (TunneledInterruptedException e) {
throw (InterruptedException)new InterruptedException(e.getMessage()).initCause(e);
- } catch (AbortException e) {
- throw e; // pass through so that the caller can catch it as AbortException
- } catch (IOException e) {
- // wrap it into a new IOException so that we get the caller's stack trace as well.
- throw new IOException("remote file operation failed: " + remote + " at " + channel + ": " + e, e);
}
} else {
// the file is on the local machine.
@@ -1094,23 +1172,28 @@ public final class FilePath implements Serializable {
* @since 1.522
*/
public Callable asCallableWith(final FileCallable task) {
- return new Callable() {
- @Override
- public V call() throws IOException {
- try {
- return act(task);
- } catch (InterruptedException e) {
- throw (IOException)new InterruptedIOException().initCause(e);
- }
+ return new CallableWith<>(task);
+ }
+ private class CallableWith implements Callable {
+ private final FileCallable task;
+ CallableWith(FileCallable task) {
+ this.task = task;
+ }
+ @Override
+ public V call() throws IOException {
+ try {
+ return act(task);
+ } catch (InterruptedException e) {
+ throw (IOException)new InterruptedIOException().initCause(e);
}
+ }
- @Override
- public void checkRoles(RoleChecker checker) throws SecurityException {
- task.checkRoles(checker);
- }
+ @Override
+ public void checkRoles(RoleChecker checker) throws SecurityException {
+ task.checkRoles(checker);
+ }
- private static final long serialVersionUID = 1L;
- };
+ private static final long serialVersionUID = 1L;
}
/**
@@ -1118,12 +1201,14 @@ public final class FilePath implements Serializable {
* on which this file is available.
*/
public URI toURI() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public URI invoke(File f, VirtualChannel channel) {
- return f.toURI();
- }
- });
+ return act(new ToURI());
+ }
+ private static class ToURI extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public URI invoke(File f, VirtualChannel channel) {
+ return f.toURI();
+ }
}
/**
@@ -1156,45 +1241,52 @@ public final class FilePath implements Serializable {
* Creates this directory.
*/
public void mkdirs() throws IOException, InterruptedException {
- if(!act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- if(mkdirs(f) || f.exists())
- return true; // OK
+ if (!act(new Mkdirs())) {
+ throw new IOException("Failed to mkdirs: " + remote);
+ }
+ }
+ private class Mkdirs extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ if(mkdirs(f) || f.exists())
+ return true; // OK
- // following Ant task to avoid possible race condition.
- Thread.sleep(10);
+ // following Ant task to avoid possible race condition.
+ Thread.sleep(10);
- return f.mkdirs() || f.exists();
- }
- }))
- throw new IOException("Failed to mkdirs: "+remote);
+ return mkdirs(f) || f.exists();
+ }
}
/**
* Deletes this directory, including all its contents recursively.
*/
public void deleteRecursive() throws IOException, InterruptedException {
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- deleteRecursive(deleting(f));
- return null;
- }
- });
+ act(new DeleteRecursive());
+ }
+ private class DeleteRecursive extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ deleteRecursive(deleting(f));
+ return null;
+ }
}
/**
* Deletes all the contents of this directory, but not the directory itself
*/
public void deleteContents() throws IOException, InterruptedException {
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- deleteContentsRecursive(f);
- return null;
- }
- });
+ act(new DeleteContents());
+ }
+ private class DeleteContents extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ deleteContentsRecursive(f);
+ return null;
+ }
}
private void deleteRecursive(File dir) throws IOException {
@@ -1305,17 +1397,25 @@ public final class FilePath implements Serializable {
*/
public FilePath createTempFile(final String prefix, final String suffix) throws IOException, InterruptedException {
try {
- return new FilePath(this,act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File dir, VirtualChannel channel) throws IOException {
- File f = writing(File.createTempFile(prefix, suffix, dir));
- return f.getName();
- }
- }));
+ return new FilePath(this, act(new CreateTempFile(prefix, suffix)));
} catch (IOException e) {
throw new IOException("Failed to create a temp file on "+remote,e);
}
}
+ private class CreateTempFile extends SecureFileCallable {
+ private final String prefix;
+ private final String suffix;
+ CreateTempFile(String prefix, String suffix) {
+ this.prefix = prefix;
+ this.suffix = suffix;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public String invoke(File dir, VirtualChannel channel) throws IOException {
+ File f = writing(File.createTempFile(prefix, suffix, dir));
+ return f.getName();
+ }
+ }
/**
* Creates a temporary file in this directory and set the contents to the
@@ -1361,30 +1461,42 @@ public final class FilePath implements Serializable {
*/
public FilePath createTextTempFile(final String prefix, final String suffix, final String contents, final boolean inThisDirectory) throws IOException, InterruptedException {
try {
- return new FilePath(channel,act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File dir, VirtualChannel channel) throws IOException {
- if(!inThisDirectory)
- dir = new File(System.getProperty("java.io.tmpdir"));
- else
- mkdirs(dir);
+ return new FilePath(channel, act(new CreateTextTempFile(inThisDirectory, prefix, suffix, contents)));
+ } catch (IOException e) {
+ throw new IOException("Failed to create a temp file on "+remote,e);
+ }
+ }
+ private final class CreateTextTempFile extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private final boolean inThisDirectory;
+ private final String prefix;
+ private final String suffix;
+ private final String contents;
+ CreateTextTempFile(boolean inThisDirectory, String prefix, String suffix, String contents) {
+ this.inThisDirectory = inThisDirectory;
+ this.prefix = prefix;
+ this.suffix = suffix;
+ this.contents = contents;
+ }
+ @Override
+ public String invoke(File dir, VirtualChannel channel) throws IOException {
+ if(!inThisDirectory)
+ dir = new File(System.getProperty("java.io.tmpdir"));
+ else
+ mkdirs(dir);
- File f;
- try {
- f = creating(File.createTempFile(prefix, suffix, dir));
- } catch (IOException e) {
- throw new IOException("Failed to create a temporary directory in "+dir,e);
- }
+ File f;
+ try {
+ f = creating(File.createTempFile(prefix, suffix, dir));
+ } catch (IOException e) {
+ throw new IOException("Failed to create a temporary directory in "+dir,e);
+ }
- try (Writer w = new FileWriter(writing(f))) {
- w.write(contents);
- }
+ try (Writer w = new FileWriter(writing(f))) {
+ w.write(contents);
+ }
- return f.getAbsolutePath();
- }
- }));
- } catch (IOException e) {
- throw new IOException("Failed to create a temp file on "+remote,e);
+ return f.getAbsolutePath();
}
}
@@ -1400,23 +1512,47 @@ public final class FilePath implements Serializable {
* @return
* The new FilePath pointing to the temporary directory
* @since 1.311
- * @see File#createTempFile(String, String)
+ * @see Files#createTempDirectory(Path, String, FileAttribute[])
*/
public FilePath createTempDir(final String prefix, final String suffix) throws IOException, InterruptedException {
try {
- return new FilePath(this,act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File dir, VirtualChannel channel) throws IOException {
- File f = File.createTempFile(prefix, suffix, dir);
- f.delete();
- f.mkdir();
- return f.getName();
- }
- }));
+ String[] s;
+ if (StringUtils.isBlank(suffix)) {
+ s = new String[]{prefix, "tmp"}; // see File.createTempFile - tmp is used if suffix is null
+ } else {
+ s = new String[]{prefix, suffix};
+ }
+ String name = StringUtils.join(s, ".");
+ return new FilePath(this, act(new CreateTempDir(name)));
} catch (IOException e) {
throw new IOException("Failed to create a temp directory on "+remote,e);
}
}
+ private class CreateTempDir extends SecureFileCallable {
+ private final String name;
+ CreateTempDir(String name) {
+ this.name = name;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public String invoke(File dir, VirtualChannel channel) throws IOException {
+
+ Path tempPath;
+ final boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
+
+ if (isPosix) {
+ tempPath = Files.createTempDirectory(Util.fileToPath(dir), name,
+ PosixFilePermissions.asFileAttribute(EnumSet.allOf(PosixFilePermission.class)));
+ } else {
+ tempPath = Files.createTempDirectory(Util.fileToPath(dir), name, new FileAttribute>[] {});
+ }
+
+ if (tempPath.toFile() == null) {
+ throw new IOException("Failed to obtain file from path " + dir + " on " + remote);
+ }
+ return tempPath.toFile().getName();
+ }
+ }
/**
* Deletes this file.
@@ -1424,26 +1560,30 @@ public final class FilePath implements Serializable {
* @return true, for a modicum of compatibility
*/
public boolean delete() throws IOException, InterruptedException {
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- Util.deleteFile(deleting(f));
- return null;
- }
- });
+ act(new Delete());
return true;
}
+ private class Delete extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ Util.deleteFile(deleting(f));
+ return null;
+ }
+ }
/**
* Checks if the file exists.
*/
public boolean exists() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Boolean invoke(File f, VirtualChannel channel) throws IOException {
- return stating(f).exists();
- }
- });
+ return act(new Exists());
+ }
+ private class Exists extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Boolean invoke(File f, VirtualChannel channel) throws IOException {
+ return stating(f).exists();
+ }
}
/**
@@ -1454,12 +1594,14 @@ public final class FilePath implements Serializable {
* @see #touch(long)
*/
public long lastModified() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Long invoke(File f, VirtualChannel channel) throws IOException {
- return stating(f).lastModified();
- }
- });
+ return act(new LastModified());
+ }
+ private class LastModified extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Long invoke(File f, VirtualChannel channel) throws IOException {
+ return stating(f).lastModified();
+ }
}
/**
@@ -1468,8 +1610,15 @@ public final class FilePath implements Serializable {
* @since 1.299
*/
public void touch(final long timestamp) throws IOException, InterruptedException {
- act(new SecureFileCallable() {
+ act(new Touch(timestamp));
+ }
+ private class Touch extends SecureFileCallable {
+ private final long timestamp;
+ Touch(long timestamp) {
+ this.timestamp = timestamp;
+ }
private static final long serialVersionUID = -5094638816500738429L;
+ @Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
if(!f.exists()) {
try {
@@ -1482,12 +1631,22 @@ public final class FilePath implements Serializable {
throw new IOException("Failed to set the timestamp of "+f+" to "+timestamp);
return null;
}
- });
}
private void setLastModifiedIfPossible(final long timestamp) throws IOException, InterruptedException {
- String message = act(new SecureFileCallable() {
+ String message = act(new SetLastModified(timestamp));
+
+ if (message!=null) {
+ LOGGER.warning(message);
+ }
+ }
+ private class SetLastModified extends SecureFileCallable {
+ private final long timestamp;
+ SetLastModified(long timestamp) {
+ this.timestamp = timestamp;
+ }
private static final long serialVersionUID = -828220335793641630L;
+ @Override
public String invoke(File f, VirtualChannel channel) throws IOException {
if(!writing(f).setLastModified(timestamp)) {
if (Functions.isWindows()) {
@@ -1500,23 +1659,20 @@ public final class FilePath implements Serializable {
}
return null;
}
- });
-
- if (message!=null) {
- LOGGER.warning(message);
- }
}
/**
* Checks if the file is a directory.
*/
public boolean isDirectory() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Boolean invoke(File f, VirtualChannel channel) throws IOException {
- return stating(f).isDirectory();
- }
- });
+ return act(new IsDirectory());
+ }
+ private final class IsDirectory extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Boolean invoke(File f, VirtualChannel channel) throws IOException {
+ return stating(f).isDirectory();
+ }
}
/**
@@ -1525,12 +1681,14 @@ public final class FilePath implements Serializable {
* @since 1.129
*/
public long length() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Long invoke(File f, VirtualChannel channel) throws IOException {
- return stating(f).length();
- }
- });
+ return act(new Length());
+ }
+ private class Length extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Long invoke(File f, VirtualChannel channel) throws IOException {
+ return stating(f).length();
+ }
}
/**
@@ -1538,12 +1696,14 @@ public final class FilePath implements Serializable {
* @since 1.542
*/
public long getFreeDiskSpace() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- @Override public Long invoke(File f, VirtualChannel channel) throws IOException {
- return f.getFreeSpace();
- }
- });
+ return act(new GetFreeDiskSpace());
+ }
+ private static class GetFreeDiskSpace extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Long invoke(File f, VirtualChannel channel) throws IOException {
+ return f.getFreeSpace();
+ }
}
/**
@@ -1551,12 +1711,14 @@ public final class FilePath implements Serializable {
* @since 1.542
*/
public long getTotalDiskSpace() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- @Override public Long invoke(File f, VirtualChannel channel) throws IOException {
- return f.getTotalSpace();
- }
- });
+ return act(new GetTotalDiskSpace());
+ }
+ private static class GetTotalDiskSpace extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Long invoke(File f, VirtualChannel channel) throws IOException {
+ return f.getTotalSpace();
+ }
}
/**
@@ -1564,12 +1726,14 @@ public final class FilePath implements Serializable {
* @since 1.542
*/
public long getUsableDiskSpace() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- @Override public Long invoke(File f, VirtualChannel channel) throws IOException {
- return f.getUsableSpace();
- }
- });
+ return act(new GetUsableDiskSpace());
+ }
+ private static class GetUsableDiskSpace extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Long invoke(File f, VirtualChannel channel) throws IOException {
+ return f.getUsableSpace();
+ }
}
/**
@@ -1583,32 +1747,46 @@ 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()
*/
public void chmod(final int mask) throws IOException, InterruptedException {
if(!isUnix() || mask==-1) return;
- 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);
+ act(new Chmod(mask));
+ }
+ private class Chmod extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private final int mask;
+ Chmod(int mask) {
+ this.mask = mask;
+ }
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ _chmod(writing(f), mask);
- return null;
- }
- });
+ return null;
+ }
}
/**
- * 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(fileToPath(f), Util.modeToPermissions(mask));
+ }
}
private static boolean CHMOD_WARNED = false;
@@ -1623,12 +1801,14 @@ public final class FilePath implements Serializable {
*/
public int mode() throws IOException, InterruptedException, PosixException {
if(!isUnix()) return -1;
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Integer invoke(File f, VirtualChannel channel) throws IOException {
- return IOUtils.mode(stating(f));
- }
- });
+ return act(new Mode());
+ }
+ private class Mode extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Integer invoke(File f, VirtualChannel channel) throws IOException {
+ return IOUtils.mode(stating(f));
+ }
}
/**
@@ -1673,8 +1853,15 @@ public final class FilePath implements Serializable {
if (filter != null && !(filter instanceof Serializable)) {
throw new IllegalArgumentException("Non-serializable filter of " + filter.getClass());
}
- return act(new SecureFileCallable>() {
+ return act(new ListFilter(filter), (filter != null ? filter : this).getClass().getClassLoader());
+ }
+ private class ListFilter extends SecureFileCallable> {
+ private final FileFilter filter;
+ ListFilter(FileFilter filter) {
+ this.filter = filter;
+ }
private static final long serialVersionUID = 1L;
+ @Override
public List invoke(File f, VirtualChannel channel) throws IOException {
File[] children = reading(f).listFiles(filter);
if (children == null) {
@@ -1687,7 +1874,6 @@ public final class FilePath implements Serializable {
return r;
}
- }, (filter!=null?filter:this).getClass().getClassLoader());
}
/**
@@ -1731,8 +1917,19 @@ public final class FilePath implements Serializable {
*/
@Nonnull
public FilePath[] list(final String includes, final String excludes, final boolean defaultExcludes) throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
+ return act(new ListGlob(includes, excludes, defaultExcludes));
+ }
+ private class ListGlob extends SecureFileCallable {
+ private final String includes;
+ private final String excludes;
+ private final boolean defaultExcludes;
+ ListGlob(String includes, String excludes, boolean defaultExcludes) {
+ this.includes = includes;
+ this.excludes = excludes;
+ this.defaultExcludes = defaultExcludes;
+ }
private static final long serialVersionUID = 1L;
+ @Override
public FilePath[] invoke(File f, VirtualChannel channel) throws IOException {
String[] files = glob(reading(f), includes, excludes, defaultExcludes);
@@ -1742,7 +1939,6 @@ public final class FilePath implements Serializable {
return r;
}
- });
}
/**
@@ -1757,7 +1953,12 @@ public final class FilePath implements Serializable {
throw new IOException("Expecting Ant GLOB pattern, but saw '"+includes+"'. See http://ant.apache.org/manual/Types/fileset.html for syntax");
FileSet fs = Util.createFileSet(dir,includes,excludes);
fs.setDefaultexcludes(defaultExcludes);
- DirectoryScanner ds = fs.getDirectoryScanner(new Project());
+ DirectoryScanner ds;
+ try {
+ ds = fs.getDirectoryScanner(new Project());
+ } catch (BuildException x) {
+ throw new IOException(x.getMessage());
+ }
String[] files = ds.getIncludedFiles();
return files;
}
@@ -1775,25 +1976,29 @@ public final class FilePath implements Serializable {
}
final Pipe p = Pipe.createRemoteToLocal();
- actAsync(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- try (InputStream fis = Files.newInputStream(reading(f).toPath());
- OutputStream out = p.getOut()) {
- org.apache.commons.io.IOUtils.copy(fis, out);
- } catch (InvalidPathException e) {
- p.error(new IOException(e));
- } catch (Exception x) {
- p.error(x);
- }
- return null;
- }
- });
+ actAsync(new Read(p));
return p.getIn();
}
+ private class Read extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private final Pipe p;
+ Read(Pipe p) {
+ this.p = p;
+ }
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ try (InputStream fis = Files.newInputStream(reading(f).toPath());
+ OutputStream out = p.getOut()) {
+ org.apache.commons.io.IOUtils.copy(fis, out);
+ } catch (InvalidPathException e) {
+ p.error(new IOException(e));
+ } catch (Exception x) {
+ p.error(x);
+ }
+ return null;
+ }
+ }
/**
* Reads this file from the specific offset.
@@ -1892,8 +2097,11 @@ public final class FilePath implements Serializable {
}
}
- return act(new SecureFileCallable() {
+ return act(new WritePipe());
+ }
+ private class WritePipe extends SecureFileCallable {
private static final long serialVersionUID = 1L;
+ @Override
public OutputStream invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
f = f.getAbsoluteFile();
mkdirs(f.getParentFile());
@@ -1904,7 +2112,6 @@ public final class FilePath implements Serializable {
throw new IOException(e);
}
}
- });
}
/**
@@ -1915,19 +2122,27 @@ public final class FilePath implements Serializable {
* @since 1.105
*/
public void write(final String content, final String encoding) throws IOException, InterruptedException {
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- mkdirs(f.getParentFile());
- try (OutputStream fos = Files.newOutputStream(writing(f).toPath());
- Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos)) {
- w.write(content);
- } catch (InvalidPathException e) {
- throw new IOException(e);
- }
- return null;
+ act(new Write(encoding, content));
+ }
+ private class Write extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private final String encoding;
+ private final String content;
+ Write(String encoding, String content) {
+ this.encoding = encoding;
+ this.content = content;
+ }
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ mkdirs(f.getParentFile());
+ try (OutputStream fos = Files.newOutputStream(writing(f).toPath());
+ Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos)) {
+ w.write(content);
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
}
- });
+ return null;
+ }
}
/**
@@ -1935,12 +2150,14 @@ public final class FilePath implements Serializable {
* @see Util#getDigestOf(File)
*/
public String digest() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File f, VirtualChannel channel) throws IOException {
- return Util.getDigestOf(reading(f));
- }
- });
+ return act(new Digest());
+ }
+ private class Digest extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public String invoke(File f, VirtualChannel channel) throws IOException {
+ return Util.getDigestOf(reading(f));
+ }
}
/**
@@ -1951,13 +2168,19 @@ public final class FilePath implements Serializable {
if(this.channel != target.channel) {
throw new IOException("renameTo target must be on the same host");
}
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- reading(f).renameTo(creating(new File(target.remote)));
- return null;
- }
- });
+ act(new RenameTo(target));
+ }
+ private class RenameTo extends SecureFileCallable {
+ private final FilePath target;
+ RenameTo(FilePath target) {
+ this.target = target;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ Files.move(fileToPath(reading(f)), fileToPath(creating(new File(target.remote))), LinkOption.NOFOLLOW_LINKS);
+ return null;
+ }
}
/**
@@ -1969,8 +2192,15 @@ public final class FilePath implements Serializable {
if(this.channel != target.channel) {
throw new IOException("pullUpTo target must be on the same host");
}
- act(new SecureFileCallable() {
+ act(new MoveAllChildrenTo(target));
+ }
+ private class MoveAllChildrenTo extends SecureFileCallable {
+ private final FilePath target;
+ MoveAllChildrenTo(FilePath target) {
+ this.target = target;
+ }
private static final long serialVersionUID = 1L;
+ @Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
// JENKINS-16846: if f.getName() is the same as one of the files/directories in f,
// then the rename op will fail
@@ -1979,7 +2209,7 @@ public final class FilePath implements Serializable {
throw new IOException("Failed to rename "+f+" to "+tmp);
File t = new File(target.getRemote());
-
+
for(File child : reading(tmp).listFiles()) {
File target = new File(t, child.getName());
if(!stating(child).renameTo(creating(target)))
@@ -1988,7 +2218,6 @@ public final class FilePath implements Serializable {
deleting(tmp).delete();
return null;
}
- });
}
/**
@@ -2009,11 +2238,32 @@ 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 CopyToWithPermission(target));
+ return;
+ }
+
copyTo(target);
// copy file permission
target.chmod(mode());
target.setLastModifiedIfPossible(lastModified());
}
+ private class CopyToWithPermission extends SecureFileCallable {
+ private final FilePath target;
+ CopyToWithPermission(FilePath target) {
+ this.target = target;
+ }
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ File targetFile = new File(target.remote);
+ File targetDir = targetFile.getParentFile();
+ filterNonNull().mkdirs(targetDir);
+ Files.createDirectories(fileToPath(targetDir));
+ Files.copy(fileToPath(reading(f)), fileToPath(writing(targetFile)), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
+ return null;
+ }
+ }
/**
* Sends the contents of this file into the given {@link OutputStream}.
@@ -2021,28 +2271,34 @@ public final class FilePath implements Serializable {
public void copyTo(OutputStream os) throws IOException, InterruptedException {
final OutputStream out = new RemoteOutputStream(os);
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 4088559042349254141L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- try (InputStream fis = Files.newInputStream(reading(f).toPath())) {
- org.apache.commons.io.IOUtils.copy(fis, out);
- return null;
- } catch (InvalidPathException e) {
- throw new IOException(e);
- } finally {
- out.close();
- }
- }
- });
+ act(new CopyTo(out));
// make sure the writes fully got delivered to 'os' before we return.
// this is needed because I/O operation is asynchronous
syncIO();
}
+ private class CopyTo extends SecureFileCallable {
+ private static final long serialVersionUID = 4088559042349254141L;
+ private final OutputStream out;
+ CopyTo(OutputStream out) {
+ this.out = out;
+ }
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ try (InputStream fis = Files.newInputStream(reading(f).toPath())) {
+ org.apache.commons.io.IOUtils.copy(fis, out);
+ return null;
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
+ } finally {
+ out.close();
+ }
+ }
+ }
/**
* With fix to JENKINS-11251 (remoting 2.15), this is no longer necessary.
- * But I'm keeping it for a while so that users who manually deploy slave.jar has time to deploy new version
+ * But I'm keeping it for a while so that users who manually deploy agent.jar has time to deploy new version
* before this goes away.
*/
private void syncIO() throws InterruptedException {
@@ -2050,10 +2306,10 @@ public final class FilePath implements Serializable {
if (channel!=null)
channel.syncLocalIO();
} catch (AbstractMethodError e) {
- // legacy slave.jar. Handle this gracefully
+ // legacy agent.jar. Handle this gracefully
try {
- LOGGER.log(Level.WARNING,"Looks like an old slave.jar. Please update "+ Which.jarFile(Channel.class)+" to the new version",e);
- } catch (IOException _) {
+ LOGGER.log(Level.WARNING,"Looks like an old agent.jar. Please update "+ Which.jarFile(Channel.class)+" to the new version",e);
+ } catch (IOException ignored) {
// really ignore this time
}
}
@@ -2136,83 +2392,31 @@ public final class FilePath implements Serializable {
public int copyRecursiveTo(final DirScanner scanner, final FilePath target, final String description) throws IOException, InterruptedException {
if(this.channel==target.channel) {
// local to local copy.
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Integer invoke(File base, VirtualChannel channel) throws IOException {
- if(!base.exists()) return 0;
- assert target.channel==null;
- final File dest = new File(target.remote);
- final AtomicInteger count = new AtomicInteger();
- scanner.scan(base, reading(new FileVisitor() {
- @Override
- public void visit(File f, String relativePath) throws IOException {
- if (f.isFile()) {
- File target = new File(dest, relativePath);
- mkdirsE(target.getParentFile());
- Util.copyFile(f, writing(target));
- count.incrementAndGet();
- }
- }
-
- @Override
- public boolean understandsSymlink() {
- return true;
- }
-
- @Override
- public void visitSymlink(File link, String target, String relativePath) throws IOException {
- try {
- mkdirsE(new File(dest, relativePath).getParentFile());
- writing(new File(dest, target));
- Util.createSymlink(dest, target, relativePath, TaskListener.NULL);
- } catch (InterruptedException x) {
- throw (IOException) new IOException(x.toString()).initCause(x);
- }
- count.incrementAndGet();
- }
- }));
- return count.get();
- }
- });
+ return act(new CopyRecursiveLocal(target, scanner));
} else
if(this.channel==null) {
// local -> remote copy
final Pipe pipe = Pipe.createLocalToRemote();
- Future future = target.actAsync(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- try (InputStream in = pipe.getIn()) {
- readFromTar(remote + '/' + description, f,TarCompression.GZIP.extract(in));
- return null;
- }
- }
- });
- Future future2 = actAsync(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- @Override public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- return writeToTar(new File(remote), scanner, TarCompression.GZIP.compress(pipe.getOut()));
- }
- });
+ Future future = target.actAsync(new ReadToTar(pipe, description));
+ Future future2 = actAsync(new WriteToTar(scanner, pipe));
try {
// JENKINS-9540 in case the reading side failed, report that error first
future.get();
return future2.get();
} catch (ExecutionException e) {
- throw new IOException(e);
+ Throwable cause = e.getCause();
+ if (cause == null) cause = e;
+ throw cause instanceof IOException
+ ? (IOException) cause
+ : new IOException(cause)
+ ;
}
} else {
// remote -> local copy
final Pipe pipe = Pipe.createRemoteToLocal();
- Future future = actAsync(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Integer invoke(File f, VirtualChannel channel) throws IOException {
- try (OutputStream out = pipe.getOut()) {
- return writeToTar(f, scanner, TarCompression.GZIP.compress(out));
- }
- }
- });
+ Future future = actAsync(new CopyRecursiveRemoteToLocal(pipe, scanner));
try {
readFromTar(remote + '/' + description,new File(target.remote),TarCompression.GZIP.extract(pipe.getIn()));
} catch (IOException e) {// BuildException or IOException
@@ -2221,20 +2425,136 @@ public final class FilePath implements Serializable {
throw e; // the remote side completed successfully, so the error must be local
} catch (ExecutionException x) {
// report both errors
- throw new IOException(Functions.printThrowable(e),x);
- } catch (TimeoutException _) {
- // remote is hanging
+ e.addSuppressed(x);
+ throw e;
+ } catch (TimeoutException ignored) {
+ // remote is hanging, just throw the original exception
throw e;
}
}
try {
return future.get();
} catch (ExecutionException e) {
- throw new IOException(e);
+ Throwable cause = e.getCause();
+ if (cause == null) cause = e;
+ throw cause instanceof IOException
+ ? (IOException) cause
+ : new IOException(cause)
+ ;
+ }
+ }
+ }
+ private class CopyRecursiveLocal extends SecureFileCallable {
+ private final FilePath target;
+ private final DirScanner scanner;
+ CopyRecursiveLocal(FilePath target, DirScanner scanner) {
+ this.target = target;
+ this.scanner = scanner;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Integer invoke(File base, VirtualChannel channel) throws IOException {
+ if (!base.exists()) {
+ return 0;
+ }
+ assert target.channel == null;
+ final File dest = new File(target.remote);
+ final AtomicInteger count = new AtomicInteger();
+ scanner.scan(base, reading(new FileVisitor() {
+ private boolean exceptionEncountered;
+ private boolean logMessageShown;
+ @Override
+ public void visit(File f, String relativePath) throws IOException {
+ if (f.isFile()) {
+ File target = new File(dest, relativePath);
+ mkdirsE(target.getParentFile());
+ Path targetPath = fileToPath(writing(target));
+ exceptionEncountered = exceptionEncountered || !tryCopyWithAttributes(f, targetPath);
+ if (exceptionEncountered) {
+ Files.copy(fileToPath(f), targetPath, StandardCopyOption.REPLACE_EXISTING);
+ if (!logMessageShown) {
+ LOGGER.log(Level.INFO,
+ "JENKINS-52325: Jenkins failed to retain attributes when copying to {0}, so proceeding without attributes.",
+ dest.getAbsolutePath());
+ logMessageShown = true;
+ }
+ }
+ count.incrementAndGet();
+ }
+ }
+ private boolean tryCopyWithAttributes(File f, Path targetPath) {
+ try {
+ Files.copy(fileToPath(f), targetPath,
+ StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ LOGGER.log(Level.FINE, "Unable to copy: {0}", e.getMessage());
+ return false;
+ }
+ return true;
+ }
+ @Override
+ public boolean understandsSymlink() {
+ return true;
+ }
+ @Override
+ public void visitSymlink(File link, String target, String relativePath) throws IOException {
+ try {
+ mkdirsE(new File(dest, relativePath).getParentFile());
+ writing(new File(dest, target));
+ Util.createSymlink(dest, target, relativePath, TaskListener.NULL);
+ } catch (InterruptedException x) {
+ throw new IOException(x);
+ }
+ count.incrementAndGet();
+ }
+ }));
+ return count.get();
+ }
+ }
+ private class ReadToTar extends SecureFileCallable {
+ private final Pipe pipe;
+ private final String description;
+ ReadToTar(Pipe pipe, String description) {
+ this.pipe = pipe;
+ this.description = description;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ try (InputStream in = pipe.getIn()) {
+ readFromTar(remote + '/' + description, f, TarCompression.GZIP.extract(in));
+ return null;
+ }
+ }
+ }
+ private class WriteToTar extends SecureFileCallable {
+ private final DirScanner scanner;
+ private final Pipe pipe;
+ WriteToTar(DirScanner scanner, Pipe pipe) {
+ this.scanner = scanner;
+ this.pipe = pipe;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ return writeToTar(new File(remote), scanner, TarCompression.GZIP.compress(pipe.getOut()));
+ }
+ }
+ private class CopyRecursiveRemoteToLocal extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private final Pipe pipe;
+ private final DirScanner scanner;
+ CopyRecursiveRemoteToLocal(Pipe pipe, DirScanner scanner) {
+ this.pipe = pipe;
+ this.scanner = scanner;
+ }
+ @Override
+ public Integer invoke(File f, VirtualChannel channel) throws IOException {
+ try (OutputStream out = pipe.getOut()) {
+ return writeToTar(f, scanner, TarCompression.GZIP.compress(out));
}
}
}
-
/**
* Writes files in 'this' directory to a tar stream.
@@ -2284,6 +2604,10 @@ public final class FilePath implements Serializable {
TarArchiveEntry te;
while ((te = t.getNextTarEntry()) != null) {
File f = new File(baseDir, te.getName());
+ if (!f.toPath().normalize().startsWith(baseDir.toPath())) {
+ throw new IOException(
+ "Tar " + name + " contains illegal file name that breaks out of the target directory: " + te.getName());
+ }
if (te.isDirectory()) {
mkdirs(f);
} else {
@@ -2360,7 +2684,7 @@ public final class FilePath implements Serializable {
* Default bound for {@link #validateAntFileMask(String, int, boolean)}.
* @since 1.592
*/
- public static int VALIDATE_ANT_FILE_MASK_BOUND = SystemProperties.getInteger(FilePath.class.getName() + ".VALIDATE_ANT_FILE_MASK_BOUND", 10000);
+ public static int VALIDATE_ANT_FILE_MASK_BOUND = Integer.getInteger(FilePath.class.getName() + ".VALIDATE_ANT_FILE_MASK_BOUND", 10000);
/**
* Like {@link #validateAntFileMask(String)} but performing only a bounded number of operations.
@@ -2375,9 +2699,20 @@ public final class FilePath implements Serializable {
* @throws InterruptedException not only in case of a channel failure, but also if too many operations were performed without finding any matches
* @since 1.484
*/
- public String validateAntFileMask(final String fileMasks, final int bound, final boolean caseSensitive) throws IOException, InterruptedException {
- return act(new MasterToSlaveFileCallable() {
+ public @CheckForNull String validateAntFileMask(final String fileMasks, final int bound, final boolean caseSensitive) throws IOException, InterruptedException {
+ return act(new ValidateAntFileMask(fileMasks, caseSensitive, bound));
+ }
+ private class ValidateAntFileMask extends MasterToSlaveFileCallable {
+ private final String fileMasks;
+ private final boolean caseSensitive;
+ private final int bound;
+ ValidateAntFileMask(String fileMasks, boolean caseSensitive, int bound) {
+ this.fileMasks = fileMasks;
+ this.caseSensitive = caseSensitive;
+ this.bound = bound;
+ }
private static final long serialVersionUID = 1;
+ @Override
public String invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
if(fileMasks.startsWith("~"))
return Messages.FilePath_TildaDoesntWork();
@@ -2523,7 +2858,6 @@ public final class FilePath implements Serializable {
if(idx2==-1) return idx1;
return Math.min(idx1,idx2);
}
- });
}
private static final UrlFactory DEFAULT_URL_FACTORY = new UrlFactory();
@@ -2765,6 +3099,11 @@ public final class FilePath implements Serializable {
return classLoader;
}
+ @Override
+ public String toString() {
+ return callable.toString();
+ }
+
private static final long serialVersionUID = 1L;
}
@@ -2789,11 +3128,13 @@ public final class FilePath implements Serializable {
* (User's home directory in the Unix sense) of the given channel.
*/
public static FilePath getHomeDirectory(VirtualChannel ch) throws InterruptedException, IOException {
- return ch.call(new MasterToSlaveCallable() {
- public FilePath call() throws IOException {
- return new FilePath(new File(System.getProperty("user.home")));
- }
- });
+ return ch.call(new GetHomeDirectory());
+ }
+ private static class GetHomeDirectory extends MasterToSlaveCallable {
+ @Override
+ public FilePath call() throws IOException {
+ return new FilePath(new File(System.getProperty("user.home")));
+ }
}
/**
@@ -2923,11 +3264,12 @@ public final class FilePath implements Serializable {
return f;
}
- private boolean mkdirs(File dir) {
+ private boolean mkdirs(File dir) throws IOException {
if (dir.exists()) return false;
filterNonNull().mkdirs(dir);
- return dir.mkdirs();
+ Files.createDirectories(fileToPath(dir));
+ return true;
}
private File mkdirsE(File dir) throws IOException {
@@ -2938,5 +3280,60 @@ public final class FilePath implements Serializable {
return IOUtils.mkdirs(dir);
}
+ /**
+ * Check if the relative child is really a descendant after symlink resolution if any.
+ *
+ * TODO un-restrict it in a weekly after the patch
+ */
+ @Restricted(NoExternalUse.class)
+ public boolean isDescendant(@Nonnull String potentialChildRelativePath) throws IOException, InterruptedException {
+ return act(new IsDescendant(potentialChildRelativePath));
+ }
+
+ private class IsDescendant extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private String potentialChildRelativePath;
+
+ private IsDescendant(@Nonnull String potentialChildRelativePath){
+ this.potentialChildRelativePath = potentialChildRelativePath;
+ }
+
+ @Override
+ public Boolean invoke(@Nonnull File parentFile, @Nonnull VirtualChannel channel) throws IOException, InterruptedException {
+ if (new File(potentialChildRelativePath).isAbsolute()) {
+ throw new IllegalArgumentException("Only a relative path is supported, the given path is absolute: " + potentialChildRelativePath);
+ }
+
+ Path parent = parentFile.getAbsoluteFile().toPath().normalize();
+
+ String remainingPath = potentialChildRelativePath;
+ File currentFile = parentFile;
+ while (!remainingPath.isEmpty()) {
+ File directChild = this.getDirectChild(currentFile, remainingPath);
+ File childUsingFullPath = new File(currentFile, remainingPath);
+ remainingPath = childUsingFullPath.getAbsolutePath().substring(directChild.getAbsolutePath().length());
+
+ File childFileSymbolic = Util.resolveSymlinkToFile(directChild);
+ if (childFileSymbolic == null) {
+ currentFile = directChild;
+ } else {
+ currentFile = childFileSymbolic;
+ }
+ }
+
+ //TODO could be refactored using Util#isDescendant(File, File) from 2.80+
+ Path child = currentFile.getAbsoluteFile().toPath().normalize();
+ return child.startsWith(parent);
+ }
+
+ private @CheckForNull File getDirectChild(File parentFile, String childPath){
+ File current = new File(parentFile, childPath);
+ while (current != null && !parentFile.equals(current.getParentFile())) {
+ current = current.getParentFile();
+ }
+ return current;
+ }
+ }
+
private static final SoloFilePathFilter UNRESTRICTED = SoloFilePathFilter.wrap(FilePathFilter.UNRESTRICTED);
}
diff --git a/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java b/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java
index 1e02e11f7f4dfdbedf2f00b853535725c1d43f86..9e162d666feb466d4487911fb4b2b2cdbd81a99a 100644
--- a/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java
+++ b/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java
@@ -50,12 +50,12 @@ public abstract class FileSystemProvisionerDescriptor extends Descriptortrue.
+ * perform the necessary deletion operation, and return {@code true}.
*
*
* If the workspace isn't the one created by this {@link FileSystemProvisioner}, or if the
* workspace can be simply deleted by {@link FilePath#deleteRecursive()}, then simply
- * return false to give other {@link FileSystemProvisionerDescriptor}s a chance to
+ * return {@code false} to give other {@link FileSystemProvisionerDescriptor}s a chance to
* discard them.
*
* @param ws
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index e696d40f29c511fdbd4cb0279659c7c5fd287416..ef2a7671a3f777f5f6a4b6c849b40799857ae636 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -26,6 +26,7 @@
package hudson;
import hudson.model.Slave;
+import hudson.security.*;
import jenkins.util.SystemProperties;
import hudson.cli.CLICommand;
import hudson.console.ConsoleAnnotationDescriptor;
@@ -46,6 +47,7 @@ import hudson.model.JobPropertyDescriptor;
import hudson.model.ModelObject;
import hudson.model.Node;
import hudson.model.PageDecorator;
+import jenkins.model.SimplePageDecorator;
import hudson.model.PaneStatusProperties;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterDefinition.ParameterDescriptor;
@@ -56,11 +58,6 @@ import hudson.model.View;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.search.SearchableModelObject;
-import hudson.security.AccessControlled;
-import hudson.security.AuthorizationStrategy;
-import hudson.security.GlobalSecurityConfiguration;
-import hudson.security.Permission;
-import hudson.security.SecurityRealm;
import hudson.security.captcha.CaptchaSupport;
import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.Cloud;
@@ -125,6 +122,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;
@@ -136,7 +134,6 @@ import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu;
-import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyTagException;
import org.apache.commons.jelly.Script;
@@ -399,11 +396,11 @@ public class Functions {
* is chosen, this part remains intact.
*
*
- * The 524 is the path from {@link Job} to {@link Run}.
+ * The {@code 524} is the path from {@link Job} to {@link Run}.
*
*
- * The bbb portion is the path after that till the last
- * {@link Run} subtype. The ccc portion is the part
+ * The {@code bbb} portion is the path after that till the last
+ * {@link Run} subtype. The {@code ccc} portion is the part
* after that.
*/
public static final class RunUrl {
@@ -471,6 +468,16 @@ public class Functions {
return new TreeMap