Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java delete files unix way (unlink header)

Tags:

java

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

like image 230
the.Legend Avatar asked Feb 09 '26 20:02

the.Legend


1 Answers

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:

  • Make sure you have both JNA and JNA-platform files in your classpath
  • 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

like image 73
the.Legend Avatar answered Feb 15 '26 16:02

the.Legend