From fcf500b87f0ddcd1fff0b9a0040b1be1b8a37321 Mon Sep 17 00:00:00 2001 Date: Fri, 29 Nov 2024 15:36:57 +0800 Subject: SA redact support password --- src/hotspot/share/runtime/arguments.cpp | 16 ++-- src/hotspot/share/runtime/arguments.hpp | 5 - src/hotspot/share/runtime/globals.hpp | 2 +- src/hotspot/share/services/heapRedactor.cpp | 7 +- src/hotspot/share/services/heapRedactor.hpp | 1 + .../classes/sun/jvm/hotspot/SALauncher.java | 10 +- .../classes/sun/jvm/hotspot/tools/JMap.java | 36 +++++++ .../hotspot/utilities/HeapHprofBinWriter.java | 75 ++++++++++++++- .../jvm/hotspot/utilities/HeapRedactor.java | 30 +++--- .../share/classes/sun/tools/jmap/JMap.java | 96 +++++++++++++++---- 10 files changed, 227 insertions(+), 51 deletions(-) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 42b4f90f1..f24cabb11 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -121,8 +121,6 @@ bool Arguments::_has_jimage = false; char* Arguments::_ext_dirs = NULL; -char* Arguments::_heap_dump_redact_auth = NULL; - bool PathString::set_value(const char *value) { if (_value != NULL) { FreeHeap(_value); @@ -3743,23 +3741,23 @@ jint Arguments::match_special_option_and_act(const JavaVMInitArgs* args, warning("Heap dump redacting did not setup properly, using wrong argument?"); vm_exit_during_initialization("Syntax error, expecting -XX:HeapDumpRedact=[off|names|basic|full|diyrules|annotation]",NULL); } + continue; } // heapDump redact password if(match_option(option, "-XX:RedactPassword=", &tail)) { if(tail == NULL || strlen(tail) == 0) { VerifyRedactPassword = false; - jio_fprintf(defaultStream::output_stream(), "redact password is null, disable verify heap dump authority.\n"); } else { - VerifyRedactPassword = true; - size_t redact_password_len = strlen(tail); - _heap_dump_redact_auth = NEW_C_HEAP_ARRAY(char, redact_password_len+1, mtArguments); - memcpy(_heap_dump_redact_auth, tail, redact_password_len); - _heap_dump_redact_auth[redact_password_len] = '\0'; - memset((void*)tail, '0', redact_password_len); + char* split_char = strstr(const_cast(tail), ","); + VerifyRedactPassword = !(split_char == NULL || strlen(split_char) < SALT_LEN); + } + if(!VerifyRedactPassword) { + jio_fprintf(defaultStream::output_stream(), "redact password is null or with bad format, disable verify heap dump authority.\n"); } } + #ifndef PRODUCT if (match_option(option, "-XX:+PrintFlagsWithComments")) { JVMFlag::printFlags(tty, true); diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index 6b9759906..cb2a04a2d 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -468,8 +468,6 @@ class Arguments : AllStatic { char** base_archive_path, char** top_archive_path) NOT_CDS_RETURN; - static char* _heap_dump_redact_auth; - public: // Parses the arguments, first phase static jint parse(const JavaVMInitArgs* args); @@ -555,9 +553,6 @@ class Arguments : AllStatic { // Java launcher properties static void process_sun_java_launcher_properties(JavaVMInitArgs* args); - // heap dump redact password - static const char* get_heap_dump_redact_auth() { return _heap_dump_redact_auth; } - // System properties static void init_system_properties(); diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index b51e50ddf..680e78c04 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -568,7 +568,7 @@ const intx ObjectAlignmentInBytes = 8; "verify authority for operating heapDump redact feature") \ \ product(ccstr, RedactPassword, NULL, \ - "authority for operating heapDump redact feature") \ + "authority for operating heapDump redact feature, format {password,salt}, salt length >= 8") \ \ product(ccstr, NativeMemoryTracking, DEBUG_ONLY("summary") NOT_DEBUG("off"), \ "Native memory tracking options") \ diff --git a/src/hotspot/share/services/heapRedactor.cpp b/src/hotspot/share/services/heapRedactor.cpp index 0e7b0a97c..cfb5b3f82 100644 --- a/src/hotspot/share/services/heapRedactor.cpp +++ b/src/hotspot/share/services/heapRedactor.cpp @@ -170,12 +170,15 @@ void HeapRedactor::init(outputStream* out) { * if HeapDumpRedact is NULL , jmap operation can not open redact feature without password * if HeapDumpRedact is not NULL, jmap operation can not change redact level without password **/ - if(Arguments::get_heap_dump_redact_auth() == NULL) { + char* split_char = NULL; + if(RedactPassword == NULL || (split_char = strstr(const_cast(RedactPassword), ",")) == NULL || strlen(split_char) < SALT_LEN) { VerifyRedactPassword = false; } if(VerifyRedactPassword && !_use_sys_params) { + size_t auth_len = strlen(RedactPassword); + size_t suffix_len = strlen(split_char); if(_redact_params.redact_password == NULL || - strcmp(_redact_params.redact_password, Arguments::get_heap_dump_redact_auth()) ) { + strncmp(_redact_params.redact_password, RedactPassword, auth_len-suffix_len) ) { // no password or wrong password _use_sys_params = true; if(out != NULL) { diff --git a/src/hotspot/share/services/heapRedactor.hpp b/src/hotspot/share/services/heapRedactor.hpp index 790430507..e5a5bf440 100644 --- a/src/hotspot/share/services/heapRedactor.hpp +++ b/src/hotspot/share/services/heapRedactor.hpp @@ -32,6 +32,7 @@ #endif #define MAX_MAP_FILE_LENGTH 1024 +#define SALT_LEN 9 enum HeapDumpRedactLevel { REDACT_UNKNOWN, diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java index 291e483e0..91a432574 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java @@ -322,7 +322,8 @@ public class SALauncher { Map.entry("HeapDumpRedact=", "HeapDumpRedact"), Map.entry("RedactMap=", "RedactMap"), Map.entry("RedactMapFile=", "RedactMapFile"), - Map.entry("RedactClassPath=", "RedactClassPath")); + Map.entry("RedactClassPath=", "RedactClassPath"), + Map.entry("RedactPassword", "RedactPassword")); } private static void runJMAP(String[] oldArgs) { @@ -337,6 +338,7 @@ public class SALauncher { String redactMap = newArgMap.get("RedactMap"); String redactMapFile = newArgMap.get("RedactMapFile"); String redactClassPath = newArgMap.get("RedactClassPath"); + boolean hasRedactPassword = newArgMap.containsKey("RedactPassword"); if (!requestHeapdump && (dumpfile != null)) { throw new IllegalArgumentException("Unexpected argument: dumpfile"); } @@ -359,6 +361,9 @@ public class SALauncher { if (redactClassPath != null) { command += ",RedactClassPath=" + redactClassPath; } + if(hasRedactPassword) { + command += ",RedactPassword"; + } newArgMap.put(command, null); } @@ -369,9 +374,12 @@ public class SALauncher { newArgMap.remove("RedactMap"); newArgMap.remove("RedactMapFile"); newArgMap.remove("RedactClassPath"); + newArgMap.remove("RedactPassword"); JMap.main(buildAttachArgs(newArgMap, false)); } + + private static void runJINFO(String[] oldArgs) { Map longOptsMap = Map.of("exe=", "exe", "core=", "core", diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/JMap.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/JMap.java index e52cd1fb1..fbead3ce4 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/JMap.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/JMap.java @@ -25,6 +25,9 @@ package sun.jvm.hotspot.tools; import java.io.*; +import java.nio.CharBuffer; +import java.util.regex.Pattern; + import sun.jvm.hotspot.debugger.JVMDebugger; import sun.jvm.hotspot.utilities.*; @@ -189,6 +192,9 @@ public class JMap extends Tool { redactParams.setRedactMapFile(keyValue[1]); } else if (keyValue[0].equals("RedactClassPath")) { redactParams.setRedactClassPath(keyValue[1]); + } else if (keyValue[0].equals("RedactPassword")) { + redactParams.setRedactPassword(getRedactPassword()); + } else { System.err.println("unknown option:" + keyValue[0]); @@ -226,6 +232,36 @@ public class JMap extends Tool { jmap.execute(args); } + private static CharBuffer getRedactPassword() { + CharBuffer redactPassword = CharBuffer.wrap(""); + // heap dump may need a password + Console console = System.console(); + char[] passwords = null; + if (console == null) { + return redactPassword; + } + + try { + passwords = console.readPassword("redact authority password:"); + } catch (Exception e) { + } + if(passwords == null) { + return redactPassword; + } + + try { + CharBuffer cb = CharBuffer.wrap(passwords); + String passwordPattern = "^[0-9a-zA-Z!@#$]{1,9}$"; + if(!Pattern.matches(passwordPattern, cb)) { + return redactPassword; + } + redactPassword = cb; + } catch (Exception e) { + } + + return redactPassword; + } + public boolean writeHeapHprofBin(String fileName, int gzLevel) { try { HeapHprofBinWriter hgw; diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java index e73b6f9a3..566d88646 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java @@ -28,6 +28,10 @@ import java.io.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.*; +import java.nio.CharBuffer; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; import java.util.*; import java.util.zip.*; import sun.jvm.hotspot.debugger.*; @@ -37,6 +41,10 @@ import sun.jvm.hotspot.runtime.*; import sun.jvm.hotspot.classfile.*; import sun.jvm.hotspot.gc.z.ZCollectedHeap; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + /* * This class writes Java heap in hprof binary format. This format is * used by Heap Analysis Tool (HAT). The class is heavily influenced @@ -386,6 +394,11 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter { private static final long MAX_U4_VALUE = 0xFFFFFFFFL; int serialNum = 1; + // encrypt + private static int SALT_MIN_LENGTH = 8; + private static int HASH_BIT_SIZE = 256; + private static int HASH_ITERATIONS_COUNT = 10000; + // Heap Redact private HeapRedactor heapRedactor; @@ -404,6 +417,10 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter { return heapRedactor.getHeapDumpRedactLevel(); } + public Optional getHeapDumpRedactPassword() { + return heapRedactor == null ? Optional.empty() : Optional.ofNullable(heapRedactor.getRedactPassword()); + } + private Optional lookupRedactName(String name){ return heapRedactor == null ? Optional.empty() : heapRedactor.lookupRedactName(name); } @@ -454,10 +471,66 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter { this.gzLevel = gzLevel; } + private boolean checkPassword() { + Optional redactAuthOption = getVMRedactParameter("RedactPassword"); + String redactAuth = redactAuthOption.isPresent() ? redactAuthOption.get() : null; + boolean redactAuthFlag = true; + if(redactAuth != null) { + String[] auths = redactAuth.split(","); + if(auths.length != 2) { + return redactAuthFlag; + } + + Optional passwordOption = getHeapDumpRedactPassword(); + CharBuffer password = passwordOption.isPresent() ? passwordOption.get() : CharBuffer.wrap(""); + char[] passwordChars = null; + try { + passwordChars = password.array(); + + byte[] saltBytes = auths[1].getBytes("UTF-8"); + if(saltBytes.length < SALT_MIN_LENGTH) { + return redactAuthFlag; + } + + String digestStr = getEncryptValue(passwordChars, saltBytes); + redactAuthFlag = auths[0].equals(digestStr); + } catch (Exception e) { + // ignore + redactAuthFlag = false; + } finally { + // clear all password + if(passwordChars != null) { + Arrays.fill(passwordChars, '0'); + } + } + } + + return redactAuthFlag; + } + + private String getEncryptValue(char[] passwordValue, byte[] saltBytes) throws InvalidKeySpecException, NoSuchAlgorithmException { + StringBuilder digestStrBuilder = new StringBuilder(); + + KeySpec spec = new PBEKeySpec(passwordValue, saltBytes, HASH_ITERATIONS_COUNT, HASH_BIT_SIZE); + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + SecretKey secretKey = secretKeyFactory.generateSecret(spec); + byte[] digestBytes = secretKey.getEncoded(); + for (byte b : digestBytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + digestStrBuilder.append('0'); + } + digestStrBuilder.append(hex); + } + String digestStr = digestStrBuilder.toString(); + + return digestStr; + } + public synchronized void write(String fileName) throws IOException { VM vm = VM.getVM(); - if(getHeapDumpRedactLevel() == HeapRedactor.HeapDumpRedactLevel.REDACT_UNKNOWN) { + if(getHeapDumpRedactLevel() == HeapRedactor.HeapDumpRedactLevel.REDACT_UNKNOWN || !checkPassword()) { resetRedactParams(); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java index c2a916617..5c442b2bb 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java @@ -30,6 +30,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.nio.CharBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -51,7 +52,7 @@ public class HeapRedactor { private HeapDumpRedactLevel redactLevel; private Map redactNameTable; private Map> redactClassTable; - private String redactClassFullName = null; + private String redactClassFullName = null; private Map redactValueTable; private RedactVectorNode headerNode; private RedactVectorNode currentNode; @@ -62,6 +63,7 @@ public class HeapRedactor { public static final String REDACT_MAP_PREFIX = "RedactMap="; public static final String REDACT_MAP_FILE_PREFIX = "RedactMapFile="; public static final String REDACT_CLASS_PATH_PREFIX = "RedactClassPath="; + public static final String REDACT_PASSWORD_PREFIX = "RedactPassword="; public static final String REDACT_UNKNOWN_STR = "UNKNOWN"; public static final String REDACT_OFF_STR = "OFF"; @@ -82,14 +84,6 @@ public class HeapRedactor { public static final int PATH_MAX = 4096; public static final int REDACT_VECTOR_SIZE = 1024; - public HeapRedactor(String options) { - redactLevel = HeapDumpRedactLevel.REDACT_UNKNOWN; - redactNameTable = null; - redactClassTable = null; - redactValueTable = null; - init(options); - } - public HeapRedactor(RedactParams redactParams) { this.redactParams = redactParams; redactLevel = HeapDumpRedactLevel.REDACT_UNKNOWN; @@ -167,6 +161,10 @@ public class HeapRedactor { return redactParams.getRedactClassPath(); } + public CharBuffer getRedactPassword(){ + return redactParams.getRedactPassword(); + } + public Optional> getRedactRulesTable(String key) { return Optional.ofNullable(redactClassTable == null ? null: redactClassTable.get(key)); } @@ -218,7 +216,7 @@ public class HeapRedactor { } private RedactParams parseRedactOptions(String optionStr) { - RedactParams params = new RedactParams(REDACT_OFF_OPTION, null, null, null); + RedactParams params = new RedactParams(REDACT_OFF_OPTION, null, null, null, null); if (optionStr != null) { String[] options = optionStr.split(","); for (String option : options) { @@ -321,16 +319,18 @@ public class HeapRedactor { private String redactMap; private String redactMapFile; private String redactClassPath; + private CharBuffer redactPassword; private boolean enableRedact = false; public RedactParams() { } - public RedactParams(String heapDumpRedact, String redactMap, String redactMapFile, String redactClassPath) { + public RedactParams(String heapDumpRedact, String redactMap, String redactMapFile, String redactClassPath, CharBuffer redactPassword) { this.heapDumpRedact = heapDumpRedact; this.redactMap = redactMap; this.redactMapFile = redactMapFile; this.redactClassPath = redactClassPath; + this.redactPassword = redactPassword; } @Override @@ -395,6 +395,14 @@ public class HeapRedactor { this.redactClassPath = redactClassPath; } + public CharBuffer getRedactPassword() { + return redactPassword; + } + + public void setRedactPassword(CharBuffer redactPassword) { + this.redactPassword = redactPassword; + } + public static boolean checkLauncherHeapdumpRedactSupport(String value) { String[] validValues = {REDACT_BASIC_OPTION, REDACT_NAME_OPTION, REDACT_FULL_OPTION, REDACT_DIYRULES_OPTION, REDACT_ANNOTATION_OPTION, REDACT_OFF_OPTION}; for (String validValue : validValues) { diff --git a/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java b/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java index ef4ea7152..6479863a6 100644 --- a/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java +++ b/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java @@ -25,14 +25,17 @@ package sun.tools.jmap; +import java.io.BufferedInputStream; import java.io.Console; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; import java.util.Arrays; import java.util.Collection; import java.util.regex.Pattern; @@ -43,6 +46,10 @@ import com.sun.tools.attach.AttachNotSupportedException; import sun.tools.attach.HotSpotVirtualMachine; import sun.tools.common.ProcessArgumentMatcher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + /* * This class is the main class for the JMap utility. It parses its arguments * and decides if the command should be satisfied using the VM attach mechanism @@ -51,6 +58,10 @@ import sun.tools.common.ProcessArgumentMatcher; * options are mapped to SA tools. */ public class JMap { + // encrypt + private static int SALT_MIN_LENGTH = 8; + private static int HASH_BIT_SIZE = 256; + private static int HASH_ITERATIONS_COUNT = 10000; public static void main(String[] args) throws Exception { if (args.length == 0) { @@ -250,7 +261,7 @@ public class JMap { } else if (subopt.startsWith("RedactClassPath")) { redactParams.setRedactClassPath(subopt.substring("RedactClassPath=".length())); } else if (subopt.startsWith("RedactPassword")) { - redactPassword = getRedactPassword(); + redactPassword = getRedactPassword(pid); } else { System.err.println("Fail: invalid option: '" + subopt + "'"); usage(1); @@ -282,7 +293,7 @@ public class JMap { } } - private static String getRedactPassword() { + private static String getRedactPassword(String pid) { String redactPassword = ",RedactPassword="; // heap dump may need a password Console console = System.console(); @@ -300,42 +311,85 @@ public class JMap { } String digestStr = null; - byte[] passwordBytes = null; try { CharBuffer cb = CharBuffer.wrap(passwords); String passwordPattern = "^[0-9a-zA-Z!@#$]{1,9}$"; if(!Pattern.matches(passwordPattern, cb)) { return redactPassword; } - Charset cs = Charset.forName("UTF-8"); - passwordBytes= cs.encode(cb).array(); - - StringBuilder digestStrBuilder = new StringBuilder(); - MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); - byte[] digestBytes = messageDigest.digest(passwordBytes); - for(byte b : digestBytes) { - String hex = Integer.toHexString(0xff & b); - if(hex.length() == 1) { - digestStrBuilder.append('0'); - } - digestStrBuilder.append(hex); + + String salt = getSalt(pid); + if(salt == null) { + return redactPassword; + } + byte[] saltBytes = salt.getBytes("UTF-8"); + if(saltBytes.length < SALT_MIN_LENGTH) { + return redactPassword; } - digestStr = digestStrBuilder.toString(); + digestStr = getEncryptValue(passwords, saltBytes); } catch (Exception e) { }finally { // clear all password if(passwords != null) { Arrays.fill(passwords, '0'); } - if(passwordBytes != null) { - Arrays.fill(passwordBytes, (byte) 0); - } } redactPassword += (digestStr == null ? "" : digestStr); return redactPassword; } + private static String getSalt(String pid) throws Exception { + String salt = null; + StringBuilder redactAuth = new StringBuilder(); + + VirtualMachine vm = VirtualMachine.attach(pid); + HotSpotVirtualMachine hvm = (HotSpotVirtualMachine) vm; + String flag = "RedactPassword"; + try (InputStream in = hvm.printFlag(flag); BufferedInputStream bis = new BufferedInputStream(in); + InputStreamReader isr = new InputStreamReader(bis, "UTF-8")) { + char c[] = new char[256]; + int n; + do { + n = isr.read(c); + + if (n > 0) { + redactAuth.append(n == c.length ? c : Arrays.copyOf(c, n)); + } + } while (n > 0); + } + vm.detach(); + + if(redactAuth.length() > 0) { + String[] auths = redactAuth.toString().split(","); + if(auths.length != 2) { + return salt; + } + return auths[1].trim(); + } + + return salt; + } + + private static String getEncryptValue(char[] passwordValue, byte[] saltBytes) throws InvalidKeySpecException, NoSuchAlgorithmException { + StringBuilder digestStrBuilder = new StringBuilder(); + + KeySpec spec = new PBEKeySpec(passwordValue, saltBytes, HASH_ITERATIONS_COUNT, HASH_BIT_SIZE); + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + SecretKey secretKey = secretKeyFactory.generateSecret(spec); + byte[] digestBytes = secretKey.getEncoded(); + for (byte b : digestBytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + digestStrBuilder.append('0'); + } + digestStrBuilder.append(hex); + } + String digestStr = digestStrBuilder.toString(); + + return digestStr; + } + private static void checkForUnsupportedOptions(String[] args) { // Check arguments for -F, -m, and non-numeric value // and warn the user that SA is not supported anymore -- 2.22.0