diff --git a/CHANGES.md b/CHANGES.md index 3c25f9607d..3fef7e1a1f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ Features * Added Some minor changes to MS Office samples Test and small changes to the MS Office samples Bug Fixes - [@wolftobias](https://github.com/wolftobias). * [#333](https://github.com/twall/jna/pull/333): Added `CoTaskMemAlloc`, `CoTaskMemRealloc` and `CoTaskMemFree` to `com.sun.jna.platform.win32.Ole32` - [@msteiger](https://github.com/msteiger). * [#334](https://github.com/twall/jna/pull/334): Added `com.sun.jna.platform.win32.Shell32.SHGetKnownFolderPath` and `KnownFolders` GUID constants - [@msteiger](https://github.com/msteiger). +* [#338](https://github.com/twall/jna/pull/338): Added `com.sun.jna.platform.mac.XAttr` and `com.sun.jna.platform.mac.XAttrUtil` JNA wrapper for `` for Mac OS X - [@rednoah](https://github.com/rednoah) Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/mac/XAttr.java b/contrib/platform/src/com/sun/jna/platform/mac/XAttr.java new file mode 100644 index 0000000000..69c8e888f1 --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/mac/XAttr.java @@ -0,0 +1,51 @@ +/* Copyright (c) 2014 Reinhard Pointner, All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package com.sun.jna.platform.mac; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; + +/** + * JNA wrapper for + * + */ +interface XAttr extends Library { + + // load from current image + XAttr INSTANCE = (XAttr) Native.loadLibrary(null, XAttr.class); + + // see /usr/include/sys/xattr.h + int XATTR_NOFOLLOW = 0x0001; + int XATTR_CREATE = 0x0002; + int XATTR_REPLACE = 0x0004; + int XATTR_NOSECURITY = 0x0008; + int XATTR_NODEFAULT = 0x0010; + int XATTR_SHOWCOMPRESSION = 0x0020; + int XATTR_MAXNAMELEN = 127; + String XATTR_FINDERINFO_NAME = "com.apple.FinderInfo"; + String XATTR_RESOURCEFORK_NAME = "com.apple.ResourceFork"; + + // see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getxattr.2.html + long getxattr(String path, String name, Pointer value, long size, int position, int options); + + // see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/setxattr.2.html + int setxattr(String path, String name, Pointer value, long size, int position, int options); + + // see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/removexattr.2.html + int removexattr(String path, String name, int options); + + // see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/listxattr.2.html + long listxattr(String path, Pointer namebuff, long size, int options); + +} diff --git a/contrib/platform/src/com/sun/jna/platform/mac/XAttrUtil.java b/contrib/platform/src/com/sun/jna/platform/mac/XAttrUtil.java new file mode 100644 index 0000000000..46b9548c24 --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/mac/XAttrUtil.java @@ -0,0 +1,98 @@ +/* Copyright (c) 2014 Reinhard Pointner, All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package com.sun.jna.platform.mac; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import com.sun.jna.Memory; + +public class XAttrUtil { + + public static List listXAttr(String path) { + // get required buffer size + long bufferLength = XAttr.INSTANCE.listxattr(path, null, 0, 0); + + if (bufferLength < 0) + return null; + + if (bufferLength == 0) + return new ArrayList(0); + + Memory valueBuffer = new Memory(bufferLength); + long valueLength = XAttr.INSTANCE.listxattr(path, valueBuffer, bufferLength, 0); + + if (valueLength < 0) + return null; + + return decodeStringSequence(valueBuffer.getByteBuffer(0, valueLength)); + } + + public static String getXAttr(String path, String name) { + // get required buffer size + long bufferLength = XAttr.INSTANCE.getxattr(path, name, null, 0, 0, 0); + + if (bufferLength < 0) + return null; + + Memory valueBuffer = new Memory(bufferLength); + long valueLength = XAttr.INSTANCE.getxattr(path, name, valueBuffer, bufferLength, 0, 0); + + if (valueLength < 0) + return null; + + return decodeString(valueBuffer.getByteBuffer(0, valueLength - 1)); + } + + public static int setXAttr(String path, String name, String value) { + Memory valueBuffer = encodeString(value); + return XAttr.INSTANCE.setxattr(path, name, valueBuffer, valueBuffer.size(), 0, 0); + } + + public static int removeXAttr(String path, String name) { + return XAttr.INSTANCE.removexattr(path, name, 0); + } + + protected static Memory encodeString(String s) { + // create NULL-terminated UTF-8 String + byte[] bb = s.getBytes(Charset.forName("UTF-8")); + Memory valueBuffer = new Memory(bb.length + 1); + valueBuffer.write(0, bb, 0, bb.length); + valueBuffer.setByte(valueBuffer.size() - 1, (byte) 0); + return valueBuffer; + } + + protected static String decodeString(ByteBuffer bb) { + return Charset.forName("UTF-8").decode(bb).toString(); + } + + protected static List decodeStringSequence(ByteBuffer bb) { + List names = new ArrayList(); + + bb.mark(); // first key starts from here + while (bb.hasRemaining()) { + if (bb.get() == 0) { + ByteBuffer nameBuffer = (ByteBuffer) bb.duplicate().limit(bb.position() - 1).reset(); + if (nameBuffer.hasRemaining()) { + names.add(decodeString(nameBuffer)); + } + bb.mark(); // next key starts from here + } + } + + return names; + } + +} diff --git a/contrib/platform/test/com/sun/jna/platform/mac/XAttrUtilTest.java b/contrib/platform/test/com/sun/jna/platform/mac/XAttrUtilTest.java new file mode 100644 index 0000000000..8902fe6650 --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/mac/XAttrUtilTest.java @@ -0,0 +1,117 @@ +/* Copyright (c) 2014 Reinhard Pointner, All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package com.sun.jna.platform.mac; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +import junit.framework.TestCase; + +public class XAttrUtilTest extends TestCase { + + String testPath; + + protected void setUp() throws Exception { + testPath = File.createTempFile("xattr-test", ".txt").getAbsolutePath(); + assertTrue(new File(testPath).exists()); + } + + protected void tearDown() throws Exception { + new File(testPath).delete(); + assertFalse(new File(testPath).exists()); + } + + public void testListXAttr() { + // no xattr initially + List keys = XAttrUtil.listXAttr(testPath); + assertEquals(0, keys.size()); + + // set multiple xattr + String[] names = new String[] { "Java", "Native", "Access" }; + for (int i = 0; i < names.length; i++) { + // set xattr + XAttrUtil.setXAttr(testPath, names[i], names[i]); + + // check if new xattr is listed + keys = XAttrUtil.listXAttr(testPath); + assertEquals(i + 1, keys.size()); + assertTrue(keys.contains(names[i])); + } + } + + public void testGetXAttr() { + String value = XAttrUtil.getXAttr(testPath, "JNA"); + assertNull(value); + + XAttrUtil.setXAttr(testPath, "JNA", "Java Native Access"); + value = XAttrUtil.getXAttr(testPath, "JNA"); + + assertEquals(Arrays.toString("Java Native Access".getBytes()), Arrays.toString(value.getBytes())); + } + + public void testSetXAttr() { + String value = XAttrUtil.getXAttr(testPath, "JNA"); + assertNull(value); + + XAttrUtil.setXAttr(testPath, "JNA", "Java Native Access"); + value = XAttrUtil.getXAttr(testPath, "JNA"); + assertEquals("Java Native Access", value); + + XAttrUtil.setXAttr(testPath, "JNA", "is nice"); + value = XAttrUtil.getXAttr(testPath, "JNA"); + assertEquals("is nice", value); + } + + public void testRemoveXAttr() { + XAttrUtil.setXAttr(testPath, "JNA", "Java Native Access"); + assertEquals("[JNA]", XAttrUtil.listXAttr(testPath).toString()); + + // remove xattr + XAttrUtil.removeXAttr(testPath, "JNA"); + + assertEquals("[]", XAttrUtil.listXAttr(testPath).toString()); + } + + public void testUnicode() { + String[] names = new String[] { "中文", "にほんご", "Österreichisch", "Française", "Português" }; + for (int i = 0; i < names.length; i++) { + // set xattr + XAttrUtil.setXAttr(testPath, names[i], names[i]); + + // check if new xattr is listed + List keys = XAttrUtil.listXAttr(testPath); + assertEquals(i + 1, keys.size()); + assertTrue(keys.contains(names[i])); + + String value = XAttrUtil.getXAttr(testPath, names[i]); + assertEquals(names[i], value); + } + } + + public void testLargeData() { + StringBuilder name = new StringBuilder(); + while (name.length() < XAttr.XATTR_MAXNAMELEN) { + name.append('X'); + } + + StringBuilder data = new StringBuilder(); + while (data.length() < 4 * 1024 * 1024) { + data.append('X'); + } + + XAttrUtil.setXAttr(testPath, name.toString(), data.toString()); + String value = XAttrUtil.getXAttr(testPath, name.toString()); + assertEquals(data.toString(), value.toString()); + } +}