Reputation: 686
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
Upvotes: 3
Views: 1816
Reputation: 89
File.delete
seems to behave as "unlink".
Here's an example
import java.io.*;
public class Main {
public static void main(String args[]) throws InterruptedException, IOException {
File invisibleFile = new File("invisiblefile");
invisibleFile.createNewFile();
FileWriter fw = new FileWriter(invisibleFile);
System.out.println("file created");
Thread.sleep(5000);
boolean deleted = invisibleFile.delete();
if(!deleted) {
System.out.println("error deleting file");
System.exit(1);
}
fw.write("hello");
fw.flush();
System.out.println("file deleted");
// 'ls invisiblefile' does not return anything
// but the file is still held open by the process:
// lsof -p $(ps -ef | awk '/[I]nvisibleFile.java/ {print $2}') | awk '/invisiblefile/ {print "size:" $7; print "inode:" $8}'
Thread.sleep(5000);
fw.close();
System.out.println("file closed");
Thread.sleep(5000);
// after closing file, it is completely gone
System.out.println("end");
}
}
And here's a terminal session to check the program's behaviour:
23:30:07 % java InvisibleFile.java
file created
^Z
zsh: suspended java InvisibleFile.java
23:30:11 % ls invisiblefile
invisiblefile
23:30:14 % fg %1
[1] - continued java InvisibleFile.java
file deleted
^Z
zsh: suspended java InvisibleFile.java
23:30:21 % ls invisiblefile
ls: invisiblefile: No such file or directory
23:30:23 % lsof -p $(ps -ef | awk '/[I]nvisibleFile.java/ {print $2}') | awk '/invisiblefile/ {print "size:" $7; print "inode:" $8}'
size:5
inode:33745509
23:30:30 % fg %1
[1] - continued java InvisibleFile.java
file closed
^Z
zsh: suspended java InvisibleFile.java
23:30:37 % lsof -p $(ps -ef | awk '/[I]nvisibleFile.java/ {print $2}') | awk '/invisiblefile/ {print "size:" $7; print "inode:" $8}'
23:30:42 % fg %1
[1] - continued java InvisibleFile.java
end
23:30:47 %
Upvotes: 0
Reputation: 686
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
Upvotes: 2