Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java NTFS compression attribute

I need to read and modify the 'Compressed' attribute of a file on an NTFS partition from Java. I imagined something in the java.nio.file.attribute package would do it -- hell it's a complex enough package, but I can't find this attribute.

The DosFileAttributes class has getters for the classic hidden/system/readonly/archive attributes only.

I tried Files.readAttributes which allows dynamically retrieving all attributes from a particular "attribute view". Under "dos:*" there was only the same attributes that are already available from the public methods of the DosFileAttributes class. I tried "ntfs:*" and "windows:*" but they weren't accepted as valid view names.

I also tried the UserDefinedFileAttributeView, but it gave me an empty list on any file I tried.

I wondered about shelling out to the attrib command (accepting the limitation that it wouldn't work for NTFS partitions mounted under Linux or other OSes) but that doesn't seem to support the attribute either. Help?

like image 394
Boann Avatar asked Oct 16 '13 11:10

Boann


1 Answers

Since this does seem to be missing from the standard Java API, I took a look at doing it myself with JNA. It's my first experience with JNA. It's not as pretty as I'd like and JNA's code seems to be sorely lacking in generics, but it's a hundred times better than mucking about with JNI and trying to set up awful cross-compilers for the different platforms needed (minimum x86 & x64 even if you target only one OS). It's the annoying compilation process that drove me from C++ to Java in the first place, and I hope never to have to return to it.

Anyway, this seems to work. Hopefully it will be useful to someone else too. It provides four public methods:

  • isAvailable() -- whether or not calling the other methods should work (i.e., we are on Windows and the JNA native lib loaded okay)
  • isCompressed(File)
  • setCompressed(File, boolean)
  • volumeSupportsFileCompression(File) -- asks Windows if the partition where a file is located supports [individual] file compression. E.g., it's true on NTFS, and false on FAT (USB sticks and so on).

Compression in the Windows API is done through a dedicated I/O control operation, and is not merely a "SetAttributes" call. If it were simpler (isomorphic with other file attributes) I'd have put the encryption attribute in there as well for completeness' sake, but anyway.

import java.io.File;
import java.io.IOException;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.ShortByReference;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIOptions;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinNT.HANDLE;

public class WindowsFileOps {
    private WindowsFileOps() {}


    private static interface Kernel32Extra extends StdCallLibrary {
        int COMPRESSION_FORMAT_NONE = 0x00000000;
        int COMPRESSION_FORMAT_DEFAULT = 0x00000001;
        int FSCTL_SET_COMPRESSION = 0x0009C040;

        Kernel32Extra INSTANCE = (Kernel32Extra)Native.loadLibrary("kernel32",
            Kernel32Extra.class, W32APIOptions.UNICODE_OPTIONS);

        boolean GetVolumeInformation(
            String lpRootPathName,
            Pointer lpVolumeNameBuffer,
            int nVolumeNameSize,
            IntByReference lpVolumeSerialNumber,
            IntByReference lpMaximumComponentLength,
            IntByReference lpFileSystemFlags,
            Pointer lpFileSystemNameBuffer,
            int nFileSystemNameSize
        );
    }


    private static Boolean isAvailable;
    public static boolean isAvailable() {
        if (isAvailable == null) {
            try {
                isAvailable = Kernel32.INSTANCE != null && Kernel32Extra.INSTANCE != null;
            } catch (Throwable t) {
                isAvailable = false;
            }
        }
        return isAvailable;
    }


    private static String pathString(File file) {
        // "\\?\" is a Windows API thing that enables paths longer than 260 chars
        return "\\\\?\\" + file.getAbsolutePath();
    }


    private static int getAttributes(File file) throws IOException {
        int attrib = Kernel32.INSTANCE.GetFileAttributes(pathString(file));
        if (attrib == Kernel32.INVALID_FILE_ATTRIBUTES) {
            throw new IOException("Unable to read file attributes of " + file);
        }
        return attrib;
    }


    public static boolean isCompressed(File file) throws IOException {
        return (getAttributes(file) & Kernel32.FILE_ATTRIBUTE_COMPRESSED) != 0;
    }


    public static void setCompressed(File file, boolean compressed) throws IOException {
        HANDLE hFile = Kernel32.INSTANCE.CreateFile(
            pathString(file),
            Kernel32.GENERIC_READ | Kernel32.GENERIC_WRITE,
            Kernel32.FILE_SHARE_READ,
            null,
            Kernel32.OPEN_EXISTING,
            0,
            null);
        try {
            if (!Kernel32.INSTANCE.DeviceIoControl(
                hFile,
                Kernel32Extra.FSCTL_SET_COMPRESSION,
                new ShortByReference((short)(
                    compressed
                        ? Kernel32Extra.COMPRESSION_FORMAT_DEFAULT
                        : Kernel32Extra.COMPRESSION_FORMAT_NONE
                )).getPointer(),
                2,
                null, 0,
                new IntByReference(),
                null
            )) throw new IOException("Unable to alter compression attribute of " + file);
        } finally {
            Kernel32.INSTANCE.CloseHandle(hFile);
        }
    }


    public static boolean volumeSupportsFileCompression(File file) throws IOException {
        IntByReference flags = new IntByReference();
        if (!Kernel32Extra.INSTANCE.GetVolumeInformation(
            pathString(file.getAbsoluteFile().toPath().getRoot().toFile()),
            null, 0,
            null,
            null,
            flags,
            null, 0
        )) throw new IOException("GetVolumeInformation failure");
        return (flags.getValue() & Kernel32.FILE_FILE_COMPRESSION) != 0;
    }
}
like image 68
Boann Avatar answered Oct 12 '22 01:10

Boann