Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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,21 @@ 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
Copy link
Member

Choose a reason for hiding this comment

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

This seems odd to me. The encoding parameter is about the charset used for process output, not the script itself. If z/OS expects the script to be in EBCDIC then why would you not just hard-code that?

Copy link
Author

Choose a reason for hiding this comment

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

Because we can't hardcode normally... EBCDIC is a set of codepages where symbols mapping differs. Worst thing that in EBCDIC that affects such chars like '! [ ]' Default code page is IBM-1047. but z/OS system programmer may set any other, like IBM-1025. And if for example i'll hardcode IBM-1047 code page, but system is configured in IBM-1025 i will get bad chars in a script itself and it will fail.
As example
#!/bin/bash will turn to smth like #|/bin/bash
'[ -f myfile ];' will turn to smth like 'p -f myfille y;'

So the logic implemented is following (on z/OS only)
1/ If encoding parameter is set on sh encoding: step - use it for transcode script, script output and script exit status)
2/ If no encoding specified on sh encoding: step - go to z/OS and ask what codepage is set and apply it for transcoding

Copy link
Author

Choose a reason for hiding this comment

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

I just wanted to give an option to user to manually set, encoding of the script on z/OS platform regardless of what is set on a system. But i can revert it back to Jenkins logic where sh encoding: determines only an output encoding, no problems here.

charset(zOSSystemEncodingCharset);
}
scriptEncodingCharset = zOSSystemEncodingCharset != null ? zOSSystemEncodingCharset.name() : scriptEncodingCharset;
}

ShellController c = new ShellController(ws);

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 +145,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 @@ -210,7 +228,16 @@ 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 = null;
OsType os = workspace.act(new getOsType());
if(os == OsType.ZOS) {
// 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 +295,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) != null ? Charset.forName(charset) : Charset.defaultCharset());
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 public 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;

protected 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 {

}

}
}