I'm having an issue where archival module written in Java fails to clean files shared via smb if opened by network users during cleanup. Below is simplified version of code for file cleanup:
private static boolean moveFile(String sourceFilePath, String targetFilePath) {
boolean fileStatus = false;
File sourceFile = new File(sourceFilePath );
File targetFile = new File(targetFilePath );
if(sourceFile.canRead() && sourceFile.canWrite() ) {
if(targetFile.exists()) {
fileStatus = (new File(targetFilePath)).delete();
if(!fileStatus) {
Logger.ERROR("Target deletion failed");
}
}
fileStatus = sourceFile.renameTo(new File(targetFilePath));
if(!fileStatus) {
Logger.ERROR("RenameTo method failed");
return false;
} else {
Logger.INFO("Move succeeded");
return true;
}
} else {
Logger.ERROR("Cannot read file");
return false;
}
}
It works fine when I test it in two Linux sessions: session A:
cat -v /dev/zero > sourceFile.txt
session B:
java -jar JavaUnixFileRemovalTest.jar sourceFile.txt targetFile.txt
But fails in production when working with network shares and users.
What I'd like to implement instead is to copy file to archive folder and unlink the header. This way if user still has the file opened he'll continue accessing the content, while name is removed from the file system so nobody else can see the file.
So the question is if there's a way to unlink file header in Unix by native Java means without explicitly calling unlink command
After some research I decided to approach this problem in a bit different way and cast powerful lost magic of the Ancients - that is, use native system C calls with help of JNA (Java Native Access)
Here's an example of the code with some explanations for JNA first-time users:
package com.WeLoveStackOverflow.JavaJNAUnlinkTest;
import java.io.File;
import com.sun.jna.Library;
import com.sun.jna.Native;
public class Main {
private static CStdLib cStdLib;
// Here you specify prototypes of native C methods to be called during runtime
// Because unlink(char *path) uses pointer to const char as argument, a wrapper class StringByReference is used to convert data types
// Link to other examples at the end of this post
public interface CStdLib extends Library {
int unlink(StringByReference path);
}
public static void main(String[] args) {
// Here I'm declaring libc usage, but you can link anything. Even your own libraries
cStdLib = (CStdLib)Native.loadLibrary("c", CStdLib.class);
Logger.INFO("Source file: " + args[0]);
Logger.INFO("Target file: " + args[1]);
moveFile(args[0],args[1]);
}
private static boolean moveFile(String sourceFilePath, String targetFilePath) {
boolean fileStatus = false;
File sourceFile = new File(sourceFilePath );
File targetFile = new File(targetFilePath );
if(sourceFile.canRead() && sourceFile.canWrite() ) {
if(targetFile.exists()) {
fileStatus = targetFile.delete();
if(!fileStatus) {
Logger.ERROR("Target deletion failed");
}
}
fileStatus = sourceFile.renameTo(targetFile);
if(!fileStatus) {
Logger.ERROR("RenameTo method failed");
Logger.INFO("Trying to copy file and unlink the original");
// ToDo: add copy method
// That's where we convert String to char*
StringByReference unlinkPath=new StringByReference(sourceFilePath);
int status=cStdLib.unlink(unlinkPath);
if(status==0){
Logger.INFO("Unlink succeeded");
}else {
Logger.ERROR("Unlink also failed");
return false;
}
} else {
Logger.INFO("Move succeeded");
}
} else {
Logger.ERROR("Cannot read file");
return false;
}
return true;
}
}
And class for converting data types:
package com.WeLoveStackOverflow.JavaJNAUnlinkTest;
import com.sun.jna.ptr.ByReference;
public class StringByReference extends ByReference {
public StringByReference() {
this(0);
}
public StringByReference(String str) {
super(str.length() < 4 ? 4 : str.length() + 1);
setValue(str);
}
private void setValue(String str) {
getPointer().setString(0, str);
}
}
So what we've got in the end? A nice Java unlink utility! test scenario: create a text file in session A, open it in less in session B and run java code in session A. Works as expected:
[me@server1 JavaFileTest]$ lsof | grep sourceFile
less 12611 me 4r REG 253,0 0 73 /home/me/JavaFileTest/sourceFile (deleted)
This is the article I used as a reference: http://jnaexamples.blogspot.com/2012/03/java-native-access-is-easy-way-to.html It contains other good examples of wrapping data types for C calls
Tips:
JNA 4.4.0 requires GLIBC_2.14. If you're getting this error then simply downgrade JNA (4.2.2 worked for me)
Exception in thread "main" java.lang.UnsatisfiedLinkError: /lib64/libc.so.6: version 'GLIBC_2.14' not found
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With