Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import hudson.model.TaskListener;
import hudson.tasks.Shell;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -42,10 +43,18 @@
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.File;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import hudson.remoting.VirtualChannel;
import org.kohsuke.stapler.DataBoundConstructor;
import javax.annotation.CheckForNull;
import jenkins.MasterToSlaveFileCallable;
import org.apache.commons.io.FileUtils;
import com.google.common.io.Files;



/**
* Runs a Bourne shell script on a Unix node using {@code nohup}.
Expand All @@ -54,7 +63,9 @@ public final class BourneShellScript extends FileMonitoringTask {

private static final Logger LOGGER = Logger.getLogger(BourneShellScript.class.getName());

private static enum OsType {DARWIN, UNIX, WINDOWS}
private static enum OsType {DARWIN, UNIX, WINDOWS, ZOS}

private static final String SYSTEM_DEFAULT_CHARSET = "SYSTEM_DEFAULT";

/** Number of times we will show launch diagnostics in a newly encountered workspace before going mute to save resources. */
@SuppressWarnings("FieldMayBeFinal")
Expand Down Expand Up @@ -106,12 +117,22 @@ public String getScript() {
if (script.isEmpty()) {
listener.getLogger().println("Warning: was asked to run an empty script");
}

OsType os = ws.act(new getOsType());
String scriptEncodingCharset = "UTF-8";
if(os == OsType.ZOS) {
Charset zOSSystemEncodingCharset = Charset.forName(ws.act(new getIBMzOsEncoding()));
if(SYSTEM_DEFAULT_CHARSET.equals(getCharset())) {
// Setting default charset to IBM z/OS default EBCDIC charset on z/OS if no encoding specified on sh step
charset(zOSSystemEncodingCharset);
}
scriptEncodingCharset = zOSSystemEncodingCharset.name();
}

ShellController c = new ShellController(ws);

c.isZos = (os == OsType.ZOS);
FilePath shf = c.getScriptFile(ws);

shf.write(script, "UTF-8");
shf.write(script, scriptEncodingCharset);

final Jenkins jenkins = Jenkins.getInstance();
String interpreter = "";
Expand All @@ -125,8 +146,6 @@ public String getScript() {
String scriptPath = shf.getRemote();
List<String> args = new ArrayList<>();

OsType os = ws.act(new getOsType());

if (os != OsType.DARWIN) { // JENKINS-25848
args.add("nohup");
}
Expand Down Expand Up @@ -196,6 +215,9 @@ public String getScript() {
/** Last-observed modification time of {@link getLogFile} on remote computer, in milliseconds. */
private transient long checkedTimestamp;

/** Caching zOS flag to avoid round trip calls in exitStatus() */
private boolean isZos;

private ShellController(FilePath ws) throws IOException, InterruptedException {
super(ws);
}
Expand All @@ -210,7 +232,15 @@ private FilePath pidFile(FilePath ws) throws IOException, InterruptedException {
}

@Override protected Integer exitStatus(FilePath workspace, TaskListener listener) throws IOException, InterruptedException {
Integer status = super.exitStatus(workspace, listener);
Integer status;
if(isZos) {
// We need to transcode status file from EBCDIC only on z/OS platform
FilePath statusFile = getResultFile(workspace);
status = statusFile.act(new StatusCheckWithEncoding(getCharset()));
}
else {
status = super.exitStatus(workspace, listener);
}
if (status != null) {
LOGGER.log(Level.FINE, "found exit code {0} in {1}", new Object[] {status, controlDir});
return status;
Expand Down Expand Up @@ -268,10 +298,50 @@ private static final class getOsType extends MasterToSlaveCallable<OsType,Runtim
return OsType.DARWIN;
} else if (Platform.current() == Platform.WINDOWS) {
return OsType.WINDOWS;
} else if(Platform.current() == Platform.UNIX && System.getProperty("os.name").equals("z/OS")) {
return OsType.ZOS;
} else {
return OsType.UNIX; // Default Value
}
}
private static final long serialVersionUID = 1L;
}

private static final class getIBMzOsEncoding extends MasterToSlaveCallable<String,RuntimeException> {
@Override public String call() throws RuntimeException {
// Not null on z/OS systems
return System.getProperty("ibm.system.encoding");
}
private static final long serialVersionUID = 1L;
}

/* Local copy of StatusCheck to run on z/OS */
static class StatusCheckWithEncoding extends MasterToSlaveFileCallable<Integer> {
private final String charset;
StatusCheckWithEncoding(String charset) {
this.charset = charset;
}
@Override
@CheckForNull
public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
if (f.exists() && f.length() > 0) {
try {
String fileString = Files.readFirstLine(f, Charset.forName(charset));
if (fileString == null || fileString.isEmpty()) {
return null;
} else {
fileString = fileString.trim();
if (fileString.isEmpty()) {
return null;
} else {
return Integer.parseInt(fileString);
}
}
} catch (NumberFormatException x) {
throw new IOException("corrupted content in " + f + ": " + x, x);
}
}
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ protected FileMonitoringController launchWithCookie(FilePath workspace, Launcher
charset = SYSTEM_DEFAULT_CHARSET;
}

@CheckForNull final String getCharset()
{
return charset;
}

/**
* Should start a process which sends output to {@linkplain FileMonitoringController#getLogFile(FilePath) log file}
* in the workspace and finally writes its exit code to {@linkplain FileMonitoringController#getResultFile(FilePath) result file}.
Expand Down Expand Up @@ -161,6 +166,10 @@ protected static class FileMonitoringController extends Controller { // TODO imp
/** @see FileMonitoringTask#charset */
private @CheckForNull String charset;

String getCharset() {
return charset;
}

/**
* {@link #transcodingCharset} on the remote side when using {@link #writeLog}.
* May be a wrapper for null; initialized on demand.
Expand Down Expand Up @@ -526,4 +535,4 @@ private static class Watcher implements Runnable {

}

}
}