diff --git a/lib-base/source/java/ch/systemsx/cisd/base/unix/Unix.java b/lib-base/source/java/ch/systemsx/cisd/base/unix/Unix.java index d1fbda3f5260397044c348729ce3db5b8ca758ed..64e78e721580df87b981422ec07e598c2dd21975 100644 --- a/lib-base/source/java/ch/systemsx/cisd/base/unix/Unix.java +++ b/lib-base/source/java/ch/systemsx/cisd/base/unix/Unix.java @@ -22,12 +22,8 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.nio.file.attribute.PosixFileAttributes; -import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.*; +import java.nio.file.attribute.*; import java.time.Instant; import java.util.HashMap; import java.util.HashSet; @@ -213,6 +209,44 @@ public final class Unix } } + /** + * Sets the owner of <var>linkName</var> to the specified <var>uid</var> and <var>gid</var> values. + * Does not dereference a symbolic link. + */ + public static final void setLinkOwner(String linkName, int uid, int gid) + throws IOExceptionUnchecked + { + try + { + Files.setAttribute(Path.of(linkName), "unix:uid", uid, LinkOption.NOFOLLOW_LINKS); + Files.setAttribute(Path.of(linkName), "unix:gid", gid, LinkOption.NOFOLLOW_LINKS); + } catch (IOException e) + { + throw new IOExceptionUnchecked(e); + } + } + + /** + * Sets the owner of <var>linkName</var> to the <var>uid</var> and <var>gid</var> of the specified <code>user</code>. + * Does not dereference a symbolic link. + */ + public static final void setLinkOwner(String linkName, Password user) + throws IOExceptionUnchecked + { + setLinkOwner(linkName, user.getUid(), user.getGid()); + } + + /** + * Sets the owner of <var>fileName</var> to the <var>uid</var> and <var>gid</var> of the specified <code>user</code>. + * Dereferences a symbolic link. + */ + public static final void setOwner(String fileName, Password user) + throws IOExceptionUnchecked + { + setOwner(fileName, user.getUid(), user.getGid()); + } + + public static int getUid(String path, boolean followLinks) { try @@ -312,6 +346,48 @@ public final class Unix return stat.isSymbolicLink() ? stat.tryGetSymbolicLink() : null; } + /** + * A class representing the Unix <code>passwd</code> struct. + */ + public static final class Password + { + private final String userName; + + private final int uid; + + private final int gid; + + Password(String userName, int uid, int gid) + { + this.userName = userName; + this.uid = uid; + this.gid = gid; + } + + public String getUserName() + { + return userName; + } + + public int getUid() + { + return uid; + } + + public int getGid() + { + return gid; + } + } + + public static Unix.Password tryGetUserByName(String username) throws IOExceptionUnchecked + { + int uid = getUidForUserName(username); + int gid = getGidForGroupName(username); + Password password = new Password(username, uid, gid); + return password; + } + // // File related methods // diff --git a/lib-base/sourceTest/java/ch/systemsx/cisd/base/AllTests.java b/lib-base/sourceTest/java/ch/systemsx/cisd/base/AllTests.java index 8f36f49f9209a79a916b4b9b0eed0401a70d4923..41af8ab8155890701622cf9c9ce7c2eb2f36dbf8 100644 --- a/lib-base/sourceTest/java/ch/systemsx/cisd/base/AllTests.java +++ b/lib-base/sourceTest/java/ch/systemsx/cisd/base/AllTests.java @@ -23,6 +23,9 @@ import ch.systemsx.cisd.base.io.ByteBufferRandomAccessFileTests; import ch.systemsx.cisd.base.io.RandomAccessFileImplTests; import ch.systemsx.cisd.base.mdarray.MDArrayTests; import ch.systemsx.cisd.base.namedthread.NamingThreadPoolExecutorTest; +import ch.systemsx.cisd.base.unix.Unix; +import ch.systemsx.cisd.base.unix.UnixRootTests; +import ch.systemsx.cisd.base.unix.UnixTests; /** * Run all unit tests. @@ -48,6 +51,14 @@ public class AllTests System.out.println(); NamingThreadPoolExecutorTest.main(args); System.out.println(); + if (Unix.isOperational()) + { + UnixTests.main(args); + UnixRootTests.main(args); + } else + { + System.err.println("No unix library found."); + } } } diff --git a/lib-base/sourceTest/java/ch/systemsx/cisd/base/unix/UnixRootTests.java b/lib-base/sourceTest/java/ch/systemsx/cisd/base/unix/UnixRootTests.java new file mode 100644 index 0000000000000000000000000000000000000000..472712cc61ad2f4979b641c09cc9c22c1bd9c47e --- /dev/null +++ b/lib-base/sourceTest/java/ch/systemsx/cisd/base/unix/UnixRootTests.java @@ -0,0 +1,108 @@ +package ch.systemsx.cisd.base.unix; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.Charset; + +import org.apache.commons.io.FileUtils; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.base.BuildAndEnvironmentInfo; +import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.base.unix.Unix.Stat; + +/** + * Tests of the {@link Unix} class that can only be performed as user root. + */ +public class UnixRootTests extends AbstractFileSystemTestCase +{ + @Test(groups = + { "requires_unix" }) + public void testChown() throws IOException + { + if (Unix.getUid() != 0) + { + System.out.println("Skipping test as we are not root."); + return; + } + final short accessMode = (short) 0777; + final String content = "someText\n"; + final File f = new File(workingDirectory, "someFile"); + final File s = new File(workingDirectory, "MyLink"); + FileUtils.writeStringToFile(f, content, Charset.defaultCharset()); + Unix.setAccessMode(f.getAbsolutePath(), accessMode); + final Stat info = Unix.getLinkInfo(f.getAbsolutePath()); + Unix.setOwner(f.getAbsolutePath(), info.getUid(), info.getGid()); + assertEquals(1, info.getNumberOfHardLinks()); + assertEquals(content.length(), info.getSize()); + assertEquals(accessMode, info.getPermissions()); + final Unix.Password nobody = Unix.tryGetUserByName("nobody"); + assertNotNull(nobody); + final Unix.Password daemon = Unix.tryGetUserByName("daemon"); + assertNotNull(daemon); + Unix.setOwner(f.getAbsolutePath(), nobody); + Unix.createSymbolicLink(f.getAbsolutePath(), s.getAbsolutePath()); + Unix.setLinkOwner(s.getAbsolutePath(), daemon); + + final Unix.Stat fileInfo = Unix.getFileInfo(s.getAbsolutePath()); + assertEquals(nobody.getUid(), fileInfo.getUid()); + assertEquals(nobody.getGid(), fileInfo.getGid()); + + final Unix.Stat linkInfo = Unix.getLinkInfo(s.getAbsolutePath()); + assertEquals(daemon.getUid(), linkInfo.getUid()); + assertEquals(daemon.getGid(), linkInfo.getGid()); + } + + public static void main(String[] args) throws Throwable + { + System.out.println(BuildAndEnvironmentInfo.INSTANCE); + System.out.println("Test class: " + UnixRootTests.class.getSimpleName()); + System.out.println(); + if (Unix.isOperational() == false) + { + System.err.println("No unix library found."); + System.exit(1); + } + final UnixRootTests test = new UnixRootTests(); + try + { + for (Method m : UnixRootTests.class.getMethods()) + { + final Test testAnnotation = m.getAnnotation(Test.class); + if (testAnnotation == null) + { + continue; + } + System.out.println("Running " + m.getName()); + test.setUp(); + try + { + m.invoke(test); + } catch (InvocationTargetException wrapperThrowable) + { + final Throwable th = wrapperThrowable.getCause(); + boolean exceptionFound = false; + for (Class<?> expectedExClazz : testAnnotation.expectedExceptions()) + { + if (expectedExClazz == th.getClass()) + { + exceptionFound = true; + break; + } + } + if (exceptionFound == false) + { + throw th; + } + } + } + System.out.println("Tests OK!"); + } finally + { + test.afterClass(); + } + } + +} diff --git a/lib-base/sourceTest/java/ch/systemsx/cisd/base/unix/UnixTests.java b/lib-base/sourceTest/java/ch/systemsx/cisd/base/unix/UnixTests.java index 9520db6a0744fc46834836f0a1097a0589fd8de3..0508c8a08418584ecf4c1c69e0f48ce5a18e26ec 100644 --- a/lib-base/sourceTest/java/ch/systemsx/cisd/base/unix/UnixTests.java +++ b/lib-base/sourceTest/java/ch/systemsx/cisd/base/unix/UnixTests.java @@ -16,21 +16,24 @@ package ch.systemsx.cisd.base.unix; -import ch.rinn.restrictions.Friend; -import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; -import org.apache.commons.io.FileUtils; -import org.testng.annotations.Test; - import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.charset.Charset; -import static ch.systemsx.cisd.base.unix.Unix.Stat; +import org.apache.commons.io.FileUtils; +import org.testng.annotations.Test; + +import ch.rinn.restrictions.Friend; +import ch.systemsx.cisd.base.BuildAndEnvironmentInfo; +import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.base.unix.Unix.Stat; /** * Test cases for the {@link Unix} system calls. - * - * @author Juan Fuentes + * + * @author Bernd Rinn */ @Friend(toClasses = Unix.class) public class UnixTests extends AbstractFileSystemTestCase @@ -45,7 +48,8 @@ public class UnixTests extends AbstractFileSystemTestCase super(cleanAfterMethod); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetLinkInfoRegularFile() throws IOException { final short accessMode = (short) 0777; @@ -64,13 +68,15 @@ public class UnixTests extends AbstractFileSystemTestCase assertEquals(f.lastModified()/1000, info.getLastModified()); } - @Test(expectedExceptions = NullPointerException.class) + @Test(groups = + { "requires_unix" }, expectedExceptions = NullPointerException.class) public void testGetLinkNull() throws IOException { Unix.getLinkInfo(null); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetLinkInfoDirectory() throws IOException { final File d = new File(workingDirectory, "someDir"); @@ -81,7 +87,8 @@ public class UnixTests extends AbstractFileSystemTestCase assertFalse(info.isSymbolicLink()); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetLinkInfoSymLink() throws IOException { final File f = new File(workingDirectory, "someOtherFile"); @@ -105,7 +112,8 @@ public class UnixTests extends AbstractFileSystemTestCase assertNull(info2.tryGetSymbolicLink()); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetLinkInfoSymLinkDanglingLink() throws IOException { final File s = new File(workingDirectory, "someDanglingLink"); @@ -117,34 +125,35 @@ public class UnixTests extends AbstractFileSystemTestCase assertTrue(info.isSymbolicLink()); final Stat info2 = Unix.tryGetFileInfo(s.getAbsolutePath()); assertNull(info2); - // assertEquals("No such file or directory", Unix.getLastError()); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetLinkInfoNonExistent() throws IOException { final File s = new File(workingDirectory, "nonExistent"); final Stat info = Unix.tryGetLinkInfo(s.getAbsolutePath()); assertNull(info); - // assertEquals("No such file or directory", Unix.getLastError()); final Stat info2 = Unix.tryGetFileInfo(s.getAbsolutePath()); assertNull(info2); - // assertEquals("No such file or directory", Unix.getLastError()); } - @Test(expectedExceptions = NullPointerException.class) + @Test(groups = + { "requires_unix" }, expectedExceptions = NullPointerException.class) public void testCreateSymbolicLinkNull() throws IOException { Unix.createSymbolicLink(null, null); } - @Test(expectedExceptions = NullPointerException.class) + @Test(groups = + { "requires_unix" }, expectedExceptions = NullPointerException.class) public void testCreateHardLinkNull() throws IOException { Unix.createHardLink(null, null); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetLinkInfoHardLink() throws IOException { final File f = new File(workingDirectory, "someOtherFile"); @@ -158,45 +167,52 @@ public class UnixTests extends AbstractFileSystemTestCase assertNull(info.tryGetSymbolicLink()); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetUid() { assertTrue(Unix.getUid() >= 0); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetEuid() { assertTrue(Unix.getEuid() >= 0); assertEquals(Unix.getUid(), Unix.getEuid()); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetGid() { assertTrue(Unix.getGid() >= 0); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetEgid() { assertTrue(Unix.getEgid() >= 0); assertEquals(Unix.getGid(), Unix.getEgid()); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetUidForUserName() { assertEquals(0, Unix.getUidForUserName("root")); } - @Test(expectedExceptions = NullPointerException.class) + @Test(groups = + { "requires_unix" }, expectedExceptions = NullPointerException.class) public void testGetUidForUserNameNull() throws IOException { Unix.getUidForUserName(null); } - @Test + @Test(groups = + { "requires_unix" }) public void testGetGidForGroupName() { final String rootGroup = Unix.tryGetGroupNameForGid(0); @@ -204,9 +220,85 @@ public class UnixTests extends AbstractFileSystemTestCase assertEquals(0, Unix.getGidForGroupName(rootGroup)); } - @Test(expectedExceptions = NullPointerException.class) + @Test(groups = + { "requires_unix" }, expectedExceptions = NullPointerException.class) public void testGetGidForGroupNameNull() throws IOException { Unix.getGidForGroupName(null); } + + @Test(groups = + { "requires_unix" }, expectedExceptions = NullPointerException.class) + public void testTryGetUserByNameNull() throws IOException + { + Unix.tryGetUserByName(null); + } + + public static void main(String[] args) throws Throwable + { + System.out.println(BuildAndEnvironmentInfo.INSTANCE); + System.out.println("Test class: " + UnixTests.class.getSimpleName()); + System.out.println(); + if (Unix.isOperational() == false) + { + System.err.println("No unix library found."); + System.exit(1); + } + boolean stopOnError = args.length > 0 && "stopOnError".equalsIgnoreCase(args[0]); + int failed = 0; + final UnixTests test = new UnixTests(); + try + { + for (Method m : UnixTests.class.getMethods()) + { + final Test testAnnotation = m.getAnnotation(Test.class); + if (testAnnotation == null) + { + continue; + } + System.out.println("Running " + m.getName()); + test.setUp(); + try + { + m.invoke(test); + } catch (InvocationTargetException wrapperThrowable) + { + final Throwable th = wrapperThrowable.getCause(); + boolean exceptionFound = false; + for (Class<?> expectedExClazz : testAnnotation.expectedExceptions()) + { + if (expectedExClazz == th.getClass()) + { + exceptionFound = true; + break; + } + } + if (exceptionFound == false) + { + ++failed; + System.out.println("Caught exception in method " + m.getName()); + th.printStackTrace(); + if (stopOnError) + { + System.exit(1); + } + } + } + } + if (failed == 0) + { + System.out.println("Tests OK!"); + } else + { + System.out.printf("%d tests FAILED!\n", failed); + } + } finally + { + if (failed == 0) + { + test.afterClass(); + } + } + } + }