Skip to content

Commit

Permalink
Allow optional library name randomization for Tomcat workaround
Browse files Browse the repository at this point in the history
  • Loading branch information
hedgecrw committed Apr 11, 2024
1 parent 1bc5f61 commit de84376
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 31 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,26 @@ _A platform-independent serial port access library for Java._

For usage examples, please refer to the [Usage wiki](https://github.com/Fazecast/jSerialComm/wiki/Usage-Examples).

If you intend to use the library in multiple applications simultaneously, please make sure
If you intend to use the library in multiple applications simultaneously, you may need
to set the ```fazecast.jSerialComm.appid``` property before accessing the SerialPort class
so that applications don't accidentally delete each others' temporary files during boot-up:
so that applications don't accidentally delete each others' temporary files during boot-up
(**this should almost never be necessary**):

```
System.setProperty("fazecast.jSerialComm.appid", "YOUR_APPLICATION_IDENTIFIER")
```

Alternately, if you plan to use the library within an Apache Tomcat application, please set
the ```jSerialComm.library.randomizeNativeName``` property to true before accessing the
SerialPort class to address an issue whereby the Tomcat bootloader tries to reinitialize
the library multiple times. This can either be done using ```-DjSerialComm.library.randomizeNativeName="true"```
as a command line parameter or by calling the following within your own application before
using any SerialPort functionality:

```
System.setProperty("jSerialComm.library.randomizeNativeName", "true")
```

In order to use the ```jSerialComm``` library in your own project, you must simply
include the JAR file in your build path and import it like any other
Java package using ```import com.fazecast.jSerialComm.*;```.
Expand Down
2 changes: 1 addition & 1 deletion src/main/c/Windows/SerialPort_Windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ JNIEXPORT jlong JNICALL Java_com_fazecast_jSerialComm_SerialPort_openPortNative(
{
(*env)->ReleaseStringChars(env, portNameJString, (const jchar*)portName);
checkJniError(env, __LINE__ - 1);
lastErrorLineNumber = __LINE__ - 3;
lastErrorLineNumber = __LINE__ - 4;
lastErrorNumber = (!port ? 1 : 2);
return 0;
}
Expand Down
50 changes: 26 additions & 24 deletions src/main/java/com/fazecast/jSerialComm/SerialPort.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,16 @@ public class SerialPort
{
// Determine the temporary file directories for native library storage
String[] architectures;
String libraryPath, libraryFileName;
String libraryPath, libraryFileName, extractedFileName;
final String manualLibraryPath = System.getProperty("jSerialComm.library.path", "");
final String OS = System.getProperty("os.name").toLowerCase();
final String arch = System.getProperty("os.arch").toLowerCase();
final File tempFileDirectory = new File(System.getProperty("java.io.tmpdir"), "jSerialComm" + File.separator + System.getProperty(tmpdirAppIdProperty, ".") + File.separator + versionString).getCanonicalFile();
final File userHomeDirectory = new File(System.getProperty("user.home"), ".jSerialComm" + File.separator + System.getProperty(tmpdirAppIdProperty, ".") + File.separator + versionString).getCanonicalFile();
final boolean randomizeNativeName = System.getProperty("jSerialComm.library.randomizeNativeName", "false").equalsIgnoreCase("true");
cleanUpDirectory(new File(tempFileDirectory, ".."));
cleanUpDirectory(new File(userHomeDirectory, ".."));

// Determine Operating System and architecture
if (System.getProperty("java.vm.vendor").toLowerCase().contains("android"))
{
Expand Down Expand Up @@ -177,7 +178,7 @@ else if (arch.contains("86") || arch.contains("amd"))
architectures = null;
System.exit(-1);
}

// Load platform-specific binaries for non-Android systems
if (!isAndroid)
{
Expand All @@ -191,7 +192,7 @@ else if (arch.contains("86") || arch.contains("amd"))
if (!libraryLoaded)
libraryLoaded = loadNativeLibrary(new File(manualLibraryPath, libraryFileName).getCanonicalPath(), errorMessages);
}

// Attempt to load from the system-defined library location
try
{
Expand All @@ -206,20 +207,21 @@ else if (arch.contains("86") || arch.contains("amd"))
}
catch (UnsatisfiedLinkError e) { errorMessages.add(e.getMessage()); }
catch (Exception e) { errorMessages.add(e.getMessage()); }

// Attempt to load from an existing extracted location
for (int attempt = 0; !libraryLoaded && (attempt < 2); ++attempt)
{
File nativeLibrary = new File((attempt == 0) ? tempFileDirectory : userHomeDirectory, libraryFileName);
libraryLoaded = nativeLibrary.exists() && loadNativeLibrary(nativeLibrary.getCanonicalPath(), errorMessages);
}

// Attempt to load from the expected JAR location
extractedFileName = randomizeNativeName ? ((new Date()).getTime() + "-" + libraryFileName) : libraryFileName;
for (int attempt = 0; !libraryLoaded && (attempt < 2); ++attempt)
{
// Create a temporary working directory with open permissions
deleteDirectory(new File((attempt == 0) ? tempFileDirectory : userHomeDirectory, "..").getCanonicalFile());
File tempNativeLibrary = new File((attempt == 0) ? tempFileDirectory : userHomeDirectory, libraryFileName);
File tempNativeLibrary = new File((attempt == 0) ? tempFileDirectory : userHomeDirectory, extractedFileName);
if (tempNativeLibrary.getParentFile().exists() || tempNativeLibrary.getParentFile().mkdirs())
{
tempNativeLibrary.getParentFile().setReadable(true, false);
Expand All @@ -228,7 +230,7 @@ else if (arch.contains("86") || arch.contains("amd"))
}
else
continue;

// Attempt to load the native jSerialComm library for any available architecture
for (int i = 0; !libraryLoaded && (i < architectures.length); ++i)
{
Expand All @@ -248,7 +250,7 @@ else if (arch.contains("86") || arch.contains("amd"))
tempNativeLibrary.setReadable(true, false);
tempNativeLibrary.setWritable(false, false);
tempNativeLibrary.setExecutable(true, false);

// Attempt to load the native library
errorMessages.add("Loading for arch: " + architectures[i]);
libraryLoaded = loadNativeLibrary(tempNativeLibrary.getCanonicalPath(), errorMessages);
Expand All @@ -257,7 +259,7 @@ else if (arch.contains("86") || arch.contains("amd"))
}
catch (Exception e) { e.printStackTrace(); }
}

// Throw an error if unable to load any native libraries
if (!libraryLoaded)
{
Expand Down Expand Up @@ -288,12 +290,12 @@ public void run()
}
}
catch (InterruptedException ignored) {}

// Un-initialize the native library
isShuttingDown = true;
if (!isAndroid)
uninitializeLibrary();

// Optionally delete all native library files
if (cleanUpOnShutdown)
try
Expand Down Expand Up @@ -430,7 +432,7 @@ static public boolean setAndroidContext(Object androidApp)
}
return false;
}

/**
* Causes the library to attempt to clean up after itself upon shutdown and automatically delete
* all temporary files it may have created.
Expand Down Expand Up @@ -577,11 +579,11 @@ public final boolean openPort(int safetySleepTime, int deviceSendQueueSize, int
receiveDeviceQueueSize = deviceReceiveQueueSize;
if (portHandle != 0)
return (androidPort != null) ? androidPort.configPort(this) : configPort(portHandle);

// Force a sleep to ensure that the port does not become unusable due to rapid closing/opening on the part of the user
if (safetySleepTimeMS > 0)
try { Thread.sleep(safetySleepTimeMS); } catch (Exception e) { Thread.currentThread().interrupt(); }

// If this is an Android root application, we must explicitly allow serial port access to the library
File portFile = isAndroidDelete ? new File(comPort) : null;
if (portFile != null && (!portFile.canRead() || !portFile.canWrite()))
Expand Down Expand Up @@ -613,7 +615,7 @@ public final boolean openPort(int safetySleepTime, int deviceSendQueueSize, int
try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; }
}
}

// Natively open the serial port, and start an event-based listener if registered
portHandle = (androidPort != null) ? androidPort.openPortNative(this) : openPortNative();
if ((portHandle != 0) && (serialEventListener != null))
Expand Down Expand Up @@ -675,7 +677,7 @@ public final boolean closePort()
// Stop a registered event listener
if (serialEventListener != null)
serialEventListener.stopListening();

// Natively close the port
if (portHandle != 0)
portHandle = (androidPort != null) ? androidPort.closePortNative() : closePortNative(portHandle);
Expand Down Expand Up @@ -800,7 +802,7 @@ public final int getLastErrorLocation()
{
return (androidPort != null) ? androidPort.getLastErrorLocation() : getLastErrorLocation(portHandle);
}

/**
* Returns the error number returned by the most recent native source code line that failed execution.
* <p>
Expand Down Expand Up @@ -880,7 +882,7 @@ public final int readBytes(byte[] buffer, int bytesToRead, int offset)
// Ensure that the buffer is large enough to hold the requested number of bytes
if (bytesToRead > (buffer.length - offset))
return -2;

// Read all requested bytes from native code
return (portHandle != 0) ? ((androidPort != null) ? androidPort.readBytes(buffer, bytesToRead, offset, timeoutMode, readTimeout) : readBytes(portHandle, buffer, bytesToRead, offset, timeoutMode, readTimeout)) : -1;
}
Expand Down Expand Up @@ -1055,7 +1057,7 @@ public final boolean clearDTR()
* @return Whether or not the DSR line is asserted.
*/
public final boolean getDSR() { return (portHandle != 0) && ((androidPort != null) ? androidPort.getDSR() : getDSR(portHandle)); }

/**
* Returns whether the DCD line is currently asserted.
* @return Whether or not the DCD line is asserted.
Expand Down Expand Up @@ -1427,7 +1429,7 @@ else if ((newReadTimeout > 0) && (newReadTimeout <= 100))
readTimeout = 100;
else
readTimeout = Math.round((float)newReadTimeout / 100.0f) * 100;

if (portHandle != 0)
{
if (safetySleepTimeMS > 0)
Expand Down Expand Up @@ -1680,7 +1682,7 @@ public final boolean setRs485ModeParameters(boolean useRS485Mode, boolean rs485R
rs485RxDuringTx = rxDuringTx;
rs485DelayBefore = delayBeforeSendMicroseconds;
rs485DelayAfter = delayAfterSendMicroseconds;

if (portHandle != 0)
{
if (safetySleepTimeMS > 0)
Expand Down Expand Up @@ -2000,7 +2002,7 @@ public final void stopListening()
else
configPort(portHandle);
}

public final void resetBuffers()
{
messageBytes.reset();
Expand Down Expand Up @@ -2207,7 +2209,7 @@ public final void write(byte[] b, int off, int len) throws NullPointerException,
// Always ensure that the port has not been closed
if (portHandle == 0)
throw new SerialPortIOException("This port appears to have been shutdown or disconnected.");

// Write the actual bytes to the serial port
int numWritten = writeBytes(b, len - totalNumWritten, off + totalNumWritten);
if (numWritten < 0)
Expand Down
8 changes: 4 additions & 4 deletions src/test/java/com/fazecast/jSerialComm/SerialPortTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
* SerialPortTest.java
*
* Created on: Feb 27, 2015
* Last Updated on: Jun 19, 2023
* Last Updated on: Apr 11, 2024
* Author: Will Hedgecock
*
* Copyright (C) 2012-2023 Fazecast, Inc.
* Copyright (C) 2012-2024 Fazecast, Inc.
*
* This file is part of jSerialComm.
*
Expand Down Expand Up @@ -87,13 +87,13 @@ static public void main(String[] args)
SerialPort[] ports = SerialPort.getCommPorts();
System.out.println("\nAvailable Ports:\n");
for (int i = 0; i < ports.length; ++i)
System.out.println(" [" + i + "] " + ports[i].getSystemPortName() + " (" + ports[i].getSystemPortPath() + "): " + ports[i].getDescriptivePortName() + " - " + ports[i].getPortDescription() + " @ " + ports[i].getPortLocation() + " (VID = " + ports[i].getVendorID() + ", PID = " + ports[i].getProductID() + ", Serial = " + ports[i].getSerialNumber() + ")");
System.out.println(" [" + i + "] " + ports[i].getSystemPortName() + " (" + ports[i].getSystemPortPath() + "): " + ports[i].getDescriptivePortName() + " - " + ports[i].getPortDescription() + " @ " + ports[i].getPortLocation() + " (VID = " + ports[i].getVendorID() + ", PID = " + ports[i].getProductID() + ", Serial = " + ports[i].getSerialNumber() + ", Manufacturer = " + ports[i].getManufacturer() + ")");
System.out.println("\nRe-enumerating ports again in 2 seconds...\n");
try { Thread.sleep(2000); } catch (Exception e) {}
ports = SerialPort.getCommPorts();
System.out.println("Available Ports:\n");
for (int i = 0; i < ports.length; ++i)
System.out.println(" [" + i + "] " + ports[i].getSystemPortName() + " (" + ports[i].getSystemPortPath() + "): " + ports[i].getDescriptivePortName() + " - " + ports[i].getPortDescription() + " @ " + ports[i].getPortLocation() + " (VID = " + ports[i].getVendorID() + ", PID = " + ports[i].getProductID() + ", Serial = " + ports[i].getSerialNumber() + ")");
System.out.println(" [" + i + "] " + ports[i].getSystemPortName() + " (" + ports[i].getSystemPortPath() + "): " + ports[i].getDescriptivePortName() + " - " + ports[i].getPortDescription() + " @ " + ports[i].getPortLocation() + " (VID = " + ports[i].getVendorID() + ", PID = " + ports[i].getProductID() + ", Serial = " + ports[i].getSerialNumber() + ", Manufacturer = " + ports[i].getManufacturer() + ")");
SerialPort ubxPort;
System.out.print("\nChoose your desired serial port or enter -1 to specify a port directly: ");
int serialPortChoice = -2;
Expand Down

0 comments on commit de84376

Please sign in to comment.