From be85819e05567905e7535bda7fa1851b6cae31ee Mon Sep 17 00:00:00 2001 From: juanf <juanf@ethz.ch> Date: Fri, 9 Jun 2023 19:51:28 +0200 Subject: [PATCH] SSDM-13521: Providing same test coverage for new Posix class as for Unix class --- .../ch/systemsx/cisd/common/io/Posix.java | 680 +++++++++++++++--- .../ch/systemsx/cisd/common/io/PosixTest.java | 459 +++++++++++- 2 files changed, 1017 insertions(+), 122 deletions(-) diff --git a/lib-commonbase/source/java/ch/systemsx/cisd/common/io/Posix.java b/lib-commonbase/source/java/ch/systemsx/cisd/common/io/Posix.java index 23e3c1483c8..538aa63c3e4 100644 --- a/lib-commonbase/source/java/ch/systemsx/cisd/common/io/Posix.java +++ b/lib-commonbase/source/java/ch/systemsx/cisd/common/io/Posix.java @@ -20,16 +20,18 @@ import ch.systemsx.cisd.base.exceptions.IOExceptionUnchecked; import ch.systemsx.cisd.base.unix.FileLinkType; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStreamReader; -import java.nio.file.FileSystems; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; -import java.nio.file.attribute.GroupPrincipal; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.UserPrincipal; -import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.attribute.*; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; public final class Posix @@ -40,56 +42,149 @@ public final class Posix } public static boolean isOperational() { - return true; + return File.separatorChar == '/'; //On Posix systems the value of this field is '/' } // // User related methods // + private static Integer gid = null; + public static int getGid() { - try - { - Process process = Runtime.getRuntime().exec("id -g -r"); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String output = reader.readLine(); - reader.close(); - return Integer.parseInt(output); - } catch (IOException e) + if (gid == null) { - throw new IOExceptionUnchecked(e); + try + { + Process process = Runtime.getRuntime().exec("id -g -r"); + BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + String output = reader.readLine(); + reader.close(); + gid = Integer.parseInt(output); + } catch (IOException e) + { + throw new IOExceptionUnchecked(e); + } } + return gid; } + private static Integer uid = null; + public static int getUid() throws IOExceptionUnchecked { - try + if (uid == null) { - Process process = Runtime.getRuntime().exec("id -u -r"); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String output = reader.readLine(); - reader.close(); - return Integer.parseInt(output); - } catch (IOException e) + try + { + Process process = Runtime.getRuntime().exec("id -u -r"); + BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + String output = reader.readLine(); + reader.close(); + uid = Integer.parseInt(output); + } catch (IOException e) + { + throw new IOExceptionUnchecked(e); + } + } + return uid; + } + + private static Map<String, Integer> uidByUserName = new HashMap<>(); + + /** + * Returns the uid of the <var>userName</var>, or <code>-1</code>, if no user with this name exists. + */ + public static final int getUidForUserName(String userName) + { + if (userName == null) { - throw new IOExceptionUnchecked(e); + throw new NullPointerException("userName"); + } + + if (uidByUserName.get(userName) == null) + { + try + { + Process process = Runtime.getRuntime().exec("id -u " + userName); + BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + String output = reader.readLine(); + reader.close(); + int uid = Integer.parseInt(output); + uidByUserName.put(userName, uid); + } catch (IOException e) + { + throw new IOExceptionUnchecked(e); + } } + + return uidByUserName.get(userName); } - public static int getEuid() + private static Map<String, Integer> gidByGroupName = new HashMap<>(); + + /** + * Returns the gid of the <var>groupName</var>, or <code>-1</code>, if no group with this name exists. + */ + public static final int getGidForGroupName(String groupName) { - try + if (groupName == null) { - Process process = Runtime.getRuntime().exec("id -u"); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String output = reader.readLine(); - reader.close(); - return Integer.parseInt(output); - } catch (IOException e) + throw new NullPointerException("groupName"); + } + + if (gidByGroupName.get(groupName) == null) { - throw new IOExceptionUnchecked(e); + try + { + Process process = Runtime.getRuntime().exec("id -g " + groupName); + BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + String output = reader.readLine(); + reader.close(); + int uid = Integer.parseInt(output); + gidByGroupName.put(groupName, uid); + } catch (IOException e) + { + throw new IOExceptionUnchecked(e); + } + } + + return gidByGroupName.get(groupName); + } + + private static Integer euid = null; + + public static int getEuid() + { + if (euid == null) + { + try + { + Process process = Runtime.getRuntime().exec("id -u"); + BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + String output = reader.readLine(); + reader.close(); + euid = Integer.parseInt(output); + } catch (IOException e) + { + throw new IOExceptionUnchecked(e); + } } + return euid; + } + + /** + * Returns the effective gid that determines the permissions of this process. + */ + public static final int getEgid() + { + return getGid(); } public static void setOwner(String path, int userId, int groupId) @@ -104,48 +199,165 @@ public final class Posix } } - public static int getUid(String path) + public static int getUid(String path, boolean followLinks) { try { - return (int) Files.getAttribute(Path.of(path), "unix:uid"); + if (followLinks) + { + return (int) Files.getAttribute(Path.of(path), "unix:uid"); + } else { + return (int) Files.getAttribute(Path.of(path), "unix:uid", LinkOption.NOFOLLOW_LINKS); + } } catch (IOException e) { throw new IOExceptionUnchecked(e); } } - public static int getGid(String path) + public static int getUid(String path) + { + return getUid(path, true); + } + + public static int getGid(String path, boolean followLinks) { try { - return (int) Files.getAttribute(Path.of(path), "unix:gid"); + if (followLinks) + { + return (int) Files.getAttribute(Path.of(path), "unix:gid"); + } else { + return (int) Files.getAttribute(Path.of(path), "unix:gid", LinkOption.NOFOLLOW_LINKS); + } } catch (IOException e) { throw new IOExceptionUnchecked(e); } } + public static int getGid(String path) + { + return getGid(path, true); + } + + private static Map<Integer, String> userNameByUid = new HashMap<>(); + public static String tryGetUserNameForUid(int uid) + { + if (userNameByUid.get(uid) == null) + { + try + { + Process process = Runtime.getRuntime().exec("id -un " + uid); + BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + String output = reader.readLine(); + reader.close(); + userNameByUid.put(uid, output); + uidByUserName.put(output, uid); + } catch (IOException e) + { + throw new IOExceptionUnchecked(e); + } + } + return userNameByUid.get(uid); + } + + private static Map<Integer, String> groupNameByGid = new HashMap<>(); + + public static String tryGetGroupNameForGid(int gid) + { + if (groupNameByGid.get(gid) == null) + { + try + { + Process process = Runtime.getRuntime().exec("id -gn " + gid); + BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + String output = reader.readLine(); + reader.close(); + groupNameByGid.put(gid, output); + gidByGroupName.put(output, gid); + } catch (IOException e) + { + throw new IOExceptionUnchecked(e); + } + } + return groupNameByGid.get(gid); + } + + public static Time getSystemTime() + { + return Time.getInstance(); + } + + public static String tryReadSymbolicLink(String absolutePath) + { + Stat stat = tryGetLinkInfo(absolutePath); + return stat.isSymbolicLink() ? stat.tryGetSymbolicLink() : null; + } + + /** + * Change link timestamps of a file, directory or link. Does not dereference a symbolic link. + * + * @param fileName The name of the file or link to change the timestamp of. + * @param accessTimeSecs The new access time in seconds since start of the epoch. + * @param accessTimeMicroSecs The micro-second part of the new access time. + * @param modificationTimeSecs The new modification time in seconds since start of the epoch. + * @param modificationTimeMicroSecs The micro-second part of the new modification time. + */ + public static void setLinkTimestamps(String fileName, + long accessTimeSecs, + long accessTimeMicroSecs, + long modificationTimeSecs, + long modificationTimeMicroSecs) { try { - UserPrincipalLookupService service = FileSystems.getDefault().getUserPrincipalLookupService(); - UserPrincipal user = service.lookupPrincipalByName(Integer.toString(uid)); - return user.getName(); + Instant accessTimeInstant = Instant.ofEpochSecond(accessTimeSecs).plus(accessTimeMicroSecs, ChronoUnit.MICROS); + FileTime accessTime = FileTime.from(accessTimeInstant); + Instant modifiedTimeInstant = Instant.ofEpochSecond(modificationTimeSecs).plus(modificationTimeMicroSecs, ChronoUnit.MICROS); + FileTime modifiedTime = FileTime.from(modifiedTimeInstant); + Files.getFileAttributeView(Path.of(fileName), BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS).setTimes(modifiedTime, accessTime, null); + Files.getFileAttributeView(Path.of(fileName), BasicFileAttributeView.class).setTimes(modifiedTime, accessTime, null); } catch (IOException e) { throw new IOExceptionUnchecked(e); } } - public static String tryGetGroupNameForGid(int gid) + /** + * Change file timestamps of a file, directory or link to the current time. Does not dereference a symbolic link. + * + * @param fileName The name of the file or link to change the timestamp of. + */ + public static void setLinkTimestamps(String fileName) throws IOExceptionUnchecked + { + Time time = Time.getInstance(); + setLinkTimestamps(fileName, time.getSecs(), time.getMicroSecPart(), time.getSecs(), time.getMicroSecPart()); + } + + /** + * Change file timestamps of a file, directory or link. Dereferences a symbolic link. + * + * @param fileName The name of the file or link to change the timestamp of. + * @param accessTimeSecs The new access time in seconds since start of the epoch. + * @param accessTimeMicroSecs The micro-second part of the new access time. + * @param modificationTimeSecs The new modification time in seconds since start of the epoch. + * @param modificationTimeMicroSecs The micro-second part of the new modification time. + */ + public static void setFileTimestamps(String fileName, + long accessTimeSecs, long accessTimeMicroSecs, + long modificationTimeSecs, long modificationTimeMicroSecs) throws IOExceptionUnchecked { try { - UserPrincipalLookupService service = FileSystems.getDefault().getUserPrincipalLookupService(); - GroupPrincipal group = service.lookupPrincipalByGroupName(Integer.toString(gid)); - return group.getName(); + Instant accessTimeInstant = Instant.ofEpochSecond(accessTimeSecs).plus(accessTimeMicroSecs, ChronoUnit.MICROS); + FileTime accessTime = FileTime.from(accessTimeInstant); + Instant modifiedTimeInstant = Instant.ofEpochSecond(modificationTimeSecs).plus(modificationTimeMicroSecs, ChronoUnit.MICROS); + FileTime modifiedTime = FileTime.from(modifiedTimeInstant); + Files.getFileAttributeView(Path.of(fileName), BasicFileAttributeView.class).setTimes(modifiedTime, accessTime, null); } catch (IOException e) { throw new IOExceptionUnchecked(e); @@ -156,13 +368,128 @@ public final class Posix // File related methods // + /** + * A class to represent a Unix <code>struct timespec</code> that holds a system time in nano-second resolution. + */ + public static final class Time + { + private final long secs; + + private final long nanos; + + public static Time getInstance() { + Instant now = Instant.now(); + return new Time(now); + } + + private Time(Instant now) { + this(now.getEpochSecond(), now.getNano()); + } + + private Time(FileTime fileTime) + { + this(fileTime.toInstant().getEpochSecond(), fileTime.toInstant().getNano()); + } + + private Time(long secs, long nanos) + { + this.secs = secs; + this.nanos = nanos; + } + + public long getSecs() + { + return secs; + } + + public long getNanoSecPart() + { + return nanos; + } + + public long getMicroSecPart() + { + if (nanos % 1000 >= 500) + { + return nanos / 1_000 + 1; + } else + { + return nanos / 1_000; + } + } + + public long getMilliSecPart() + { + if (nanos % 1000000 >= 500000) + { + return nanos / 1_000_000 + 1; + } else + { + return nanos / 1_000_000; + } + } + + public long getMillis() + { + return secs * 1_000 + getMilliSecPart(); + } + + @Override + public String toString() + { + return "Time [secs=" + secs + ", nanos=" + nanos + "]"; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + (int) (nanos ^ (nanos >>> 32)); + result = prime * result + (int) (secs ^ (secs >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + final Posix.Time other = (Posix.Time) obj; + if (nanos != other.nanos) + { + return false; + } + if (secs != other.secs) + { + return false; + } + return true; + } + + } + public static class Stat { + private final Path path; + private final short permissions; private final FileLinkType linkType; - private final long lastModified; + private final Time lastModified; + + private final Time lastAccessed; private final int uid; @@ -172,12 +499,14 @@ public final class Posix private final long size; - public Stat(short permissions, FileLinkType linkType, long lastModified, int uid, int gid, + public Stat(Path path, short permissions, FileLinkType linkType, FileTime lastModified, FileTime lastAccessed, int uid, int gid, String symbolicLinkOrNull, long size) { + this.path = path; this.permissions = permissions; this.linkType = linkType; - this.lastModified = lastModified; + this.lastModified = new Time(lastModified); + this.lastAccessed = new Time(lastAccessed); this.uid = uid; this.gid = gid; this.symbolicLinkOrNull = symbolicLinkOrNull; @@ -194,7 +523,22 @@ public final class Posix return linkType; } + public long getLastAccess() + { + return lastAccessed.getSecs(); + } + + public Time getLastAccessTime() + { + return lastAccessed; + } + public long getLastModified() + { + return lastModified.getSecs(); + } + + public Time getLastModifiedTime() { return lastModified; } @@ -217,94 +561,220 @@ public final class Posix public long getSize() { return size; } - } - private static short getNumericAccessMode(Set<PosixFilePermission> permissions) { - short mode = 0; - if (permissions.contains(PosixFilePermission.OWNER_READ)) { - mode |= 0400; - } - if (permissions.contains(PosixFilePermission.OWNER_WRITE)) { - mode |= 0200; - } - if (permissions.contains(PosixFilePermission.OWNER_EXECUTE)) { - mode |= 0100; - } - if (permissions.contains(PosixFilePermission.GROUP_READ)) { - mode |= 040; - } - if (permissions.contains(PosixFilePermission.GROUP_WRITE)) { - mode |= 020; - } - if (permissions.contains(PosixFilePermission.GROUP_EXECUTE)) { - mode |= 010; - } - if (permissions.contains(PosixFilePermission.OTHERS_READ)) { - mode |= 04; + public boolean isSymbolicLink() + { + return symbolicLinkOrNull != null; } - if (permissions.contains(PosixFilePermission.OTHERS_WRITE)) { - mode |= 02; + + public int getNumberOfHardLinks() throws IOException + { + Number count = (Number) Files.getAttribute(path, "unix:nlink", LinkOption.NOFOLLOW_LINKS); + return count.intValue(); } - if (permissions.contains(PosixFilePermission.OTHERS_EXECUTE)) { - mode |= 01; + } + + private static short getNumericAccessMode(Set<PosixFilePermission> permissions) { + short posixPermissions = 0; + + for (PosixFilePermission permission : permissions) { + switch (permission) { + case OWNER_READ: + posixPermissions |= 0400; + break; + case OWNER_WRITE: + posixPermissions |= 0200; + break; + case OWNER_EXECUTE: + posixPermissions |= 0100; + break; + case GROUP_READ: + posixPermissions |= 0040; + break; + case GROUP_WRITE: + posixPermissions |= 0020; + break; + case GROUP_EXECUTE: + posixPermissions |= 0010; + break; + case OTHERS_READ: + posixPermissions |= 0004; + break; + case OTHERS_WRITE: + posixPermissions |= 0002; + break; + case OTHERS_EXECUTE: + posixPermissions |= 0001; + break; + } } - return mode; + + return posixPermissions; } - private static Set<PosixFilePermission> getFilePermissionsMode(short mode) { - Set<PosixFilePermission> permissions = new HashSet<>(); - if ((0400 & mode) == 0400) { - permissions.add(PosixFilePermission.OWNER_READ); + private static Set<PosixFilePermission> getFilePermissionsMode(short permissions) { + Set<PosixFilePermission> posixPermissions = new HashSet<>(); + + if ((permissions & 0400) != 0) { + posixPermissions.add(PosixFilePermission.OWNER_READ); } - if ((0200 & mode) == 0200) { - permissions.add(PosixFilePermission.OWNER_WRITE); + if ((permissions & 0200) != 0) { + posixPermissions.add(PosixFilePermission.OWNER_WRITE); } - if ((0100 & mode) == 0100) { - permissions.add(PosixFilePermission.OWNER_EXECUTE); + if ((permissions & 0100) != 0) { + posixPermissions.add(PosixFilePermission.OWNER_EXECUTE); } - if ((040 & mode) == 040) { - permissions.add(PosixFilePermission.GROUP_READ); + if ((permissions & 0040) != 0) { + posixPermissions.add(PosixFilePermission.GROUP_READ); } - if ((020 & mode) == 020) { - permissions.add(PosixFilePermission.GROUP_WRITE); + if ((permissions & 0020) != 0) { + posixPermissions.add(PosixFilePermission.GROUP_WRITE); } - if ((010 & mode) == 010) { - permissions.add(PosixFilePermission.GROUP_EXECUTE); + if ((permissions & 0010) != 0) { + posixPermissions.add(PosixFilePermission.GROUP_EXECUTE); } - if ((04 & mode) == 04) { - permissions.add(PosixFilePermission.OTHERS_READ); + if ((permissions & 0004) != 0) { + posixPermissions.add(PosixFilePermission.OTHERS_READ); } - if ((02 & mode) == 02) { - permissions.add(PosixFilePermission.OWNER_WRITE); + if ((permissions & 0002) != 0) { + posixPermissions.add(PosixFilePermission.OTHERS_WRITE); } - if ((01 & mode) == 01) { - permissions.add(PosixFilePermission.OTHERS_EXECUTE); + if ((permissions & 0001) != 0) { + posixPermissions.add(PosixFilePermission.OTHERS_EXECUTE); } - return permissions; + + return posixPermissions; } - public static Stat tryGetLinkInfo(String pathAsString) { - try + /** + * Returns the information about <var>linkName</var>. + * + * @throws IOExceptionUnchecked If the information could not be obtained, e.g. because the link does not exist. + */ + public static Stat getLinkInfo(String absolutePath) + { + return getLinkInfo(absolutePath, true); + } + + public static Stat getLinkInfo(String pathAsString, boolean readSymbolicLinkTarget) + { + try { + if (pathAsString == null) + { + throw new NullPointerException("linkName"); + } + + Path path = Path.of(pathAsString); + if (Files.exists(path, LinkOption.NOFOLLOW_LINKS) == false) + { + return null; + } + + + BasicFileAttributes attrs = null; + FileLinkType linkType; + short permissions; + int uid; + int gid; + if(readSymbolicLinkTarget && Files.exists(path)) + { + permissions = getNumericAccessMode(Files.getPosixFilePermissions(path)); + attrs = Files.readAttributes(path, BasicFileAttributes.class); + if (Files.isSymbolicLink(path)) { + linkType = FileLinkType.SYMLINK; + } else if (Files.isDirectory(path)) { + linkType = FileLinkType.DIRECTORY; + } else if (Files.isRegularFile(path)) { + linkType = FileLinkType.REGULAR_FILE; + } else { + linkType = FileLinkType.OTHER; + } + uid = getUid(pathAsString); + gid = getGid(pathAsString); + } else { + permissions = getNumericAccessMode(Files.getPosixFilePermissions(path, LinkOption.NOFOLLOW_LINKS)); + attrs = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + if (Files.isSymbolicLink(path)) { + linkType = FileLinkType.SYMLINK; + } else if (Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) { + linkType = FileLinkType.DIRECTORY; + } else if (Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) { + linkType = FileLinkType.REGULAR_FILE; + } else { + linkType = FileLinkType.OTHER; + } + uid = getUid(pathAsString, false); + gid = getGid(pathAsString, false); + } + + FileTime lastModified = attrs.lastModifiedTime(); + FileTime lastAccessed = attrs.lastAccessTime(); + + String symbolicLinkOrNull = null; + if (linkType == FileLinkType.SYMLINK && readSymbolicLinkTarget) { + symbolicLinkOrNull = Files.readSymbolicLink(path).toString(); + } + long size = attrs.size(); + return new Stat(path, permissions, linkType, lastModified, lastAccessed, uid, gid, symbolicLinkOrNull, size); + } catch (IOException e) { + throw new IOExceptionUnchecked(e); + } + } + + public static Stat tryGetLinkInfo(String pathAsString){ + return getLinkInfo(pathAsString, true); + } + + public static Stat tryGetFileInfo(String absolutePath) + { + return getFileInfo(absolutePath, true); + } + + public static Stat getFileInfo(String pathAsString) + { + return getFileInfo(pathAsString, true); + } + + public static Stat getFileInfo(String pathAsString, boolean readSymbolicLinkTarget) + throws IOExceptionUnchecked + { + try { + if (pathAsString == null) + { + throw new NullPointerException("linkName"); + } + Path path = Path.of(pathAsString); + if (Files.exists(path) == false) + { + return null; + } + short permissions = getNumericAccessMode(Files.getPosixFilePermissions(path)); + BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); + FileLinkType linkType; - if (Files.isSymbolicLink(path)) { + if (attrs.isSymbolicLink()) { linkType = FileLinkType.SYMLINK; - } else if (Files.isDirectory(path)) { + } else if (attrs.isDirectory()) { linkType = FileLinkType.DIRECTORY; + } else if (attrs.isRegularFile()) { + linkType = FileLinkType.REGULAR_FILE; } else { linkType = FileLinkType.OTHER; } - long lastModified = Files.getLastModifiedTime(path).toMillis(); + + FileTime lastModified = attrs.lastModifiedTime(); + FileTime lastAccessed = attrs.lastAccessTime(); int uid = getUid(pathAsString); int gid = getGid(pathAsString); String symbolicLinkOrNull = null; - if (linkType == FileLinkType.SYMLINK) { + if (linkType == FileLinkType.SYMLINK && readSymbolicLinkTarget) { symbolicLinkOrNull = Files.readSymbolicLink(path).toString(); } - long size = Files.size(path); - return new Stat(permissions, linkType, lastModified, uid, gid, symbolicLinkOrNull, size); + long size = attrs.size(); + return new Stat(path, permissions, linkType, lastModified, lastAccessed, uid, gid, symbolicLinkOrNull, size); } catch (IOException e) { throw new IOExceptionUnchecked(e); @@ -398,8 +868,7 @@ public final class Posix try { Path file = Path.of(fileName); Path link = Path.of(linkName); - Files.createDirectories(link.getParent()); // Create any missing folder on the directory hierarchy leading to folder that will contain the link - Files.createSymbolicLink(link, file); // Creates the link + Files.createSymbolicLink(link, file);// Creates the link } catch (IOException exception) { throw new IOExceptionUnchecked(exception); } @@ -412,7 +881,6 @@ public final class Posix try { Path file = Path.of(fileName); Path link = Path.of(linkName); - Files.createDirectories(link.getParent()); // Create any missing folder on the directory hierarchy leading to folder that will contain the link Files.createLink(link, file); // Creates the link } catch (IOException exception) { throw new IOExceptionUnchecked(exception); diff --git a/lib-commonbase/sourceTest/java/ch/systemsx/cisd/common/io/PosixTest.java b/lib-commonbase/sourceTest/java/ch/systemsx/cisd/common/io/PosixTest.java index 6bcb80392bc..1262547cf1f 100644 --- a/lib-commonbase/sourceTest/java/ch/systemsx/cisd/common/io/PosixTest.java +++ b/lib-commonbase/sourceTest/java/ch/systemsx/cisd/common/io/PosixTest.java @@ -1,48 +1,475 @@ +/* + * Copyright 2007 - 2018 ETH Zuerich, CISD and SIS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.systemsx.cisd.common.io; +import ch.rinn.restrictions.Friend; +import ch.systemsx.cisd.base.BuildAndEnvironmentInfo; +import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.base.unix.FileLinkType; +import org.apache.commons.io.FileUtils; import org.testng.annotations.Test; import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; + +import static org.testng.Assert.assertNotEquals; +import static ch.systemsx.cisd.common.io.Posix.Stat; -public class PosixTest +/** + * Test cases for the {@link Posix} system calls. + * + * @author Juan Fuentes + */ +@Friend(toClasses = Posix.class) +public class PosixTest extends AbstractFileSystemTestCase { + private PosixTest() + { + super(); + } + + private PosixTest(boolean cleanAfterMethod) + { + super(cleanAfterMethod); + } + @Test - public void testGetEuid() + public void testGetLinkInfoRegularFile() throws IOException + { + final short accessMode = (short) 0777; + final String content = "someText\n"; + final File f = new File(workingDirectory, "someFile"); + FileUtils.writeStringToFile(f, content, Charset.defaultCharset()); + Posix.setAccessMode(f.getAbsolutePath(), accessMode); + final Stat info = Posix.getLinkInfo(f.getAbsolutePath()); + Posix.setOwner(f.getAbsolutePath(), info.getUid(), info.getGid()); + assertEquals(1, info.getNumberOfHardLinks()); + assertEquals(content.length(), info.getSize()); + assertEquals(accessMode, info.getPermissions()); + assertEquals("root", Posix.tryGetUserNameForUid(0)); + assertEquals(FileLinkType.REGULAR_FILE, info.getLinkType()); + assertFalse(info.isSymbolicLink()); + assertEquals(f.lastModified()/1000, info.getLastModified()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testGetLinkNull() throws IOException + { + Posix.getLinkInfo(null); + } + + @Test + public void testGetLinkInfoDirectory() throws IOException + { + final File d = new File(workingDirectory, "someDir"); + d.mkdir(); + final Stat info = Posix.getLinkInfo(d.getAbsolutePath()); + assertEquals(2, info.getNumberOfHardLinks()); + assertEquals(FileLinkType.DIRECTORY, info.getLinkType()); + assertFalse(info.isSymbolicLink()); + } + + @Test + public void testGetLinkInfoSymLink() throws IOException + { + final File f = new File(workingDirectory, "someOtherFile"); + final String content = "someMoreText\n"; + FileUtils.writeStringToFile(f, content, Charset.defaultCharset()); + final File s = new File(workingDirectory, "someLink"); + Posix.createSymbolicLink(f.getAbsolutePath(), s.getAbsolutePath()); + final Stat info = Posix.getLinkInfo(s.getAbsolutePath()); + assertEquals(1, info.getNumberOfHardLinks()); + assertEquals(FileLinkType.SYMLINK, info.getLinkType()); + assertTrue(info.isSymbolicLink()); + assertEquals(f.getAbsolutePath(), info.tryGetSymbolicLink()); + assertEquals(f.getAbsolutePath(), Posix.tryReadSymbolicLink(s.getAbsolutePath())); + assertNull(Posix.getLinkInfo(s.getAbsolutePath(), false).tryGetSymbolicLink()); + + final Stat info2 = Posix.getFileInfo(s.getAbsolutePath()); + assertEquals(1, info2.getNumberOfHardLinks()); + assertEquals(content.length(), info2.getSize()); + assertEquals(FileLinkType.REGULAR_FILE, info2.getLinkType()); + assertFalse(info2.isSymbolicLink()); + assertNull(info2.tryGetSymbolicLink()); + } + + @Test + public void testTouchSymLinkAndFileRealtimeTimer() throws IOException, InterruptedException + { + if (BuildAndEnvironmentInfo.INSTANCE.getOS().contains("2.6.32")) + { + System.out.println(" ...skipping as CentOS6 does not yet support the realtime timer."); + return; + } +// Posix.setUseUnixRealtimeTimer(true); + final File f = new File(workingDirectory, "someOtherFile"); + final String content = "someMoreText\n"; + FileUtils.writeStringToFile(f, content, Charset.defaultCharset()); + final File s = new File(workingDirectory, "someLink"); + Posix.createSymbolicLink(f.getAbsolutePath(), s.getAbsolutePath()); + final Stat info = Posix.getLinkInfo(s.getAbsolutePath()); + assertEquals(1, info.getNumberOfHardLinks()); + assertEquals(FileLinkType.SYMLINK, info.getLinkType()); + assertTrue(info.isSymbolicLink()); + assertEquals(f.getAbsolutePath(), info.tryGetSymbolicLink()); + assertEquals(f.getAbsolutePath(), Posix.tryReadSymbolicLink(s.getAbsolutePath())); + assertNull(Posix.getLinkInfo(s.getAbsolutePath(), false).tryGetSymbolicLink()); + final long lastMicros = info.getLastModifiedTime().getMicroSecPart(); + final long newLastModifiedLink = info.getLastModifiedTime().getSecs() - 24 * 3600; + Posix.setLinkTimestamps(s.getAbsolutePath(), newLastModifiedLink, lastMicros, newLastModifiedLink, lastMicros); + + final long newLastModifiedFile = info.getLastModifiedTime().getSecs() - 2 * 24 * 3600; + Posix.setFileTimestamps(f.getAbsolutePath(), newLastModifiedFile, lastMicros, newLastModifiedFile, lastMicros); + + final Stat info2l = Posix.getLinkInfo(s.getAbsolutePath(), false); + assertEquals(newLastModifiedLink, info2l.getLastModifiedTime().getSecs()); + assertEquals(lastMicros, info2l.getLastModifiedTime().getMicroSecPart()); + assertEquals(newLastModifiedLink, info2l.getLastAccessTime().getSecs()); + assertEquals(lastMicros, info2l.getLastAccessTime().getMicroSecPart()); + + final Stat info2f = Posix.getFileInfo(s.getAbsolutePath()); + final Stat info2f2 = Posix.getLinkInfo(f.getAbsolutePath()); + assertNotEquals(info2l.getLastModifiedTime(), info2f2.getLastModifiedTime()); + assertEquals(info2f2.getLastModifiedTime(), info2f.getLastModifiedTime()); + assertEquals(newLastModifiedFile, info2f.getLastModifiedTime().getSecs()); + assertEquals(lastMicros, info2f.getLastModifiedTime().getMicroSecPart()); + assertEquals(newLastModifiedFile, info2f.getLastAccessTime().getSecs()); + assertEquals(lastMicros, info2f.getLastAccessTime().getMicroSecPart()); + + Thread.sleep(10); + + final Posix.Time now1 = Posix.getSystemTime(); + assertNotEquals(0, now1.getNanoSecPart() % 1_000); + Posix.setLinkTimestamps(s.getAbsolutePath()); + final Posix.Time now2 = Posix.getSystemTime(); + final Stat info3 = Posix.getLinkInfo(s.getAbsolutePath()); + + assertTrue(now1.getSecs() <= info3.getLastModified() && info3.getLastModified() <= now2.getSecs()); + assertTrue(now1.getMicroSecPart() <= info3.getLastModifiedTime().getMicroSecPart() && info.getLastModifiedTime().getMilliSecPart() <= now2.getMicroSecPart()); + assertTrue(now1.getSecs() <= info3.getLastAccess() && info3.getLastAccess() <= now2.getSecs()); + assertTrue(now1.getMicroSecPart() <= info3.getLastAccessTime().getMicroSecPart() && info.getLastAccessTime().getMilliSecPart() <= now2.getMicroSecPart()); + assertNotEquals(lastMicros, info3.getLastModifiedTime().getMicroSecPart()); + assertNotEquals(lastMicros, info3.getLastAccessTime().getMicroSecPart()); + + } + + @Test + public void testTouchSymLinkAndFile() throws IOException, InterruptedException + { +// Posix.setUseUnixRealtimeTimer(false); + final File someFile = new File(workingDirectory, "someOtherFile"); + final String content = "someMoreText\n"; + FileUtils.writeStringToFile(someFile, content, Charset.defaultCharset()); + final File someSymlink = new File(workingDirectory, "someLink"); + Posix.createSymbolicLink(someFile.getAbsolutePath(), someSymlink.getAbsolutePath()); + final Stat infoSymlink = Posix.getLinkInfo(someSymlink.getAbsolutePath()); + assertEquals(1, infoSymlink.getNumberOfHardLinks()); + assertEquals(FileLinkType.SYMLINK, infoSymlink.getLinkType()); + assertTrue(infoSymlink.isSymbolicLink()); + assertEquals(someFile.getAbsolutePath(), infoSymlink.tryGetSymbolicLink()); + assertEquals(someFile.getAbsolutePath(), Posix.tryReadSymbolicLink(someSymlink.getAbsolutePath())); + assertNull(Posix.getLinkInfo(someSymlink.getAbsolutePath(), false).tryGetSymbolicLink()); + final long newLastModifiedLinkMicros = infoSymlink.getLastModifiedTime().getMicroSecPart(); + final long newLastModifiedLinkSec = infoSymlink.getLastModifiedTime().getSecs() - 24 * 3600; + Posix.setLinkTimestamps(someSymlink.getAbsolutePath(), newLastModifiedLinkSec, newLastModifiedLinkMicros, newLastModifiedLinkSec, newLastModifiedLinkMicros); + + final long newLastModifiedSecFile = infoSymlink.getLastModifiedTime().getSecs() - 2 * 24 * 3600; + Posix.setFileTimestamps(someFile.getAbsolutePath(), newLastModifiedSecFile, newLastModifiedLinkMicros, newLastModifiedSecFile, newLastModifiedLinkMicros); + + final Stat infoSymlink2 = Posix.getLinkInfo(someSymlink.getAbsolutePath(), false); + assertEquals(newLastModifiedLinkSec, infoSymlink2.getLastModifiedTime().getSecs()); + assertEquals(newLastModifiedLinkMicros, infoSymlink2.getLastModifiedTime().getMicroSecPart()); + assertEquals(newLastModifiedLinkSec, infoSymlink2.getLastAccessTime().getSecs()); + assertEquals(newLastModifiedLinkMicros, infoSymlink2.getLastAccessTime().getMicroSecPart()); + + final Stat info2f = Posix.getFileInfo(someSymlink.getAbsolutePath()); + final Stat info2f2 = Posix.getLinkInfo(someFile.getAbsolutePath()); + assertNotEquals(infoSymlink2.getLastModifiedTime(), info2f2.getLastModifiedTime()); + assertEquals(info2f2.getLastModifiedTime(), info2f.getLastModifiedTime()); + assertEquals(newLastModifiedSecFile, info2f.getLastModifiedTime().getSecs()); + assertEquals(newLastModifiedLinkMicros, info2f.getLastModifiedTime().getMicroSecPart()); + assertEquals(newLastModifiedSecFile, info2f.getLastAccessTime().getSecs()); + assertEquals(newLastModifiedLinkMicros, info2f.getLastAccessTime().getMicroSecPart()); + + + Thread.sleep(10); + + final Posix.Time now1 = Posix.getSystemTime(); + assertEquals(0, now1.getNanoSecPart() % 1_000); + Posix.setLinkTimestamps(someSymlink.getAbsolutePath()); // Modifies the link, not the linked file + final Posix.Time now2 = Posix.getSystemTime(); + final Stat info3 = Posix.getLinkInfo(someSymlink.getAbsolutePath()); // Returns the linked file info + + assertTrue(now1.getSecs() <= info3.getLastModified() && info3.getLastModified() <= now2.getSecs()); + assertTrue(now1.getMicroSecPart() <= info3.getLastModifiedTime().getMicroSecPart() && infoSymlink.getLastModifiedTime().getMilliSecPart() <= now2.getMicroSecPart()); + assertTrue(now1.getSecs() <= info3.getLastAccess() && info3.getLastAccess() <= now2.getSecs()); + assertTrue(now1.getMicroSecPart() <= info3.getLastAccessTime().getMicroSecPart() && infoSymlink.getLastAccessTime().getMilliSecPart() <= now2.getMicroSecPart()); + assertNotEquals(newLastModifiedLinkMicros, info3.getLastModifiedTime().getMicroSecPart()); + assertNotEquals(newLastModifiedLinkMicros, info3.getLastAccessTime().getMicroSecPart()); + + } + + @Test + public void testGetLinkInfoSymLinkDanglingLink() throws IOException + { + final File s = new File(workingDirectory, "someDanglingLink"); + Posix.createSymbolicLink("link_to_nowhere", s.getAbsolutePath()); + final Stat info = Posix.tryGetLinkInfo(s.getAbsolutePath()); + assertNotNull(info); + assertEquals(1, info.getNumberOfHardLinks()); + assertEquals(FileLinkType.SYMLINK, info.getLinkType()); + assertTrue(info.isSymbolicLink()); + final Stat info2 = Posix.tryGetFileInfo(s.getAbsolutePath()); + assertNull(info2); +// assertEquals("No such file or directory", Posix.getLastError()); + } + + @Test + public void testGetLinkInfoNonExistent() throws IOException + { + final File s = new File(workingDirectory, "nonExistent"); + final Stat info = Posix.tryGetLinkInfo(s.getAbsolutePath()); + assertNull(info); +// assertEquals("No such file or directory", Posix.getLastError()); + final Stat info2 = Posix.tryGetFileInfo(s.getAbsolutePath()); + assertNull(info2); +// assertEquals("No such file or directory", Posix.getLastError()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testCreateSymbolicLinkNull() throws IOException + { + Posix.createSymbolicLink(null, null); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testCreateHardLinkNull() throws IOException { - int euid = Posix.getEuid(); + Posix.createHardLink(null, null); + } + + @Test + public void testGetLinkInfoHardLink() throws IOException + { + final File f = new File(workingDirectory, "someOtherFile"); + f.createNewFile(); + final File s = new File(workingDirectory, "someLink"); + Posix.createHardLink(f.getAbsolutePath(), s.getAbsolutePath()); + final Stat info = Posix.getLinkInfo(s.getAbsolutePath()); + assertEquals(2, info.getNumberOfHardLinks()); + assertEquals(FileLinkType.REGULAR_FILE, info.getLinkType()); + assertFalse(info.isSymbolicLink()); + assertNull(info.tryGetSymbolicLink()); } @Test public void testGetUid() { - int uid = Posix.getUid(); + assertTrue(Posix.getUid() >= 0); + } + + @Test + public void testGetEuid() + { + assertTrue(Posix.getEuid() >= 0); + assertEquals(Posix.getUid(), Posix.getEuid()); } @Test public void testGetGid() { - int gid = Posix.getGid(); + assertTrue(Posix.getGid() >= 0); } @Test - public void testTryGetGroupNameForGid() { - int gid = Posix.getGid(); - String group = Posix.tryGetGroupNameForGid(gid); + public void testGetEgid() + { + assertTrue(Posix.getEgid() >= 0); + assertEquals(Posix.getGid(), Posix.getEgid()); } @Test - public void testSetOwner() throws IOException + public void testGetUidForUserName() { - File file = File.createTempFile("pre", "su"); - Posix.setOwner(file.getPath(), Posix.getUid(), Posix.getGid()); - file.delete(); + assertEquals(0, Posix.getUidForUserName("root")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testGetUidForUserNameNull() throws IOException + { + Posix.getUidForUserName(null); } @Test - public void testTryGetLinkInfo() throws IOException { - File file = File.createTempFile("pre", "su"); - Posix.Stat stat = Posix.tryGetLinkInfo(file.toString()); - file.delete(); + public void testGetGidForGroupName() + { + final String rootGroup = Posix.tryGetGroupNameForGid(0); + assertTrue(rootGroup, "root".equals(rootGroup) || "wheel".equals(rootGroup)); + assertEquals(0, Posix.getGidForGroupName(rootGroup)); } + + @Test(expectedExceptions = NullPointerException.class) + public void testGetGidForGroupNameNull() throws IOException + { + Posix.getGidForGroupName(null); + } +// +// @Test(groups = +// { "requires_unix" }) +// public void testTryGetGroupByName() +// { +// final String rootGroup = Posix.tryGetGroupNameForGid(0); +// final Group group = Posix.tryGetGroupByName(rootGroup); +// assertNotNull(group); +// assertEquals(rootGroup, group.getGroupName()); +// assertEquals(0, group.getGid()); +// assertNotNull(group.getGroupMembers()); +// } +// +// @Test(groups = +// { "requires_unix" }, expectedExceptions = NullPointerException.class) +// public void testTryGetGroupByNameNull() throws IOException +// { +// Posix.tryGetGroupByName(null); +// } +// +// @Test(groups = +// { "requires_unix" }) +// public void testTryGetGroupByGid() +// { +// final Group group = Posix.tryGetGroupByGid(0); +// assertNotNull(group); +// final String rootGroup = group.getGroupName(); +// assertTrue(rootGroup, "root".equals(rootGroup) || "wheel".equals(rootGroup)); +// assertEquals(0, group.getGid()); +// assertNotNull(group.getGroupMembers()); +// } +// +// @Test(groups = +// { "requires_unix" }) +// public void testTryGetUserByName() +// { +// final Password user = Posix.tryGetUserByName("root"); +// assertNotNull(user); +// assertEquals("root", user.getUserName()); +// assertEquals(0, user.getUid()); +// assertEquals(0, user.getGid()); +// assertNotNull(user.getUserFullName()); +// assertNotNull(user.getHomeDirectory()); +// assertNotNull(user.getShell()); +// assertTrue(user.getShell().startsWith("/")); +// } +// +// @Test(groups = +// { "requires_unix" }, expectedExceptions = NullPointerException.class) +// public void testTryGetUserByNameNull() throws IOException +// { +// Posix.tryGetUserByName(null); +// } +// +// @Test(groups = +// { "requires_unix" }) +// public void testTryGetUserByUid() +// { +// final Password user = Posix.tryGetUserByUid(0); +// assertNotNull(user); +// assertEquals("root", user.getUserName()); +// assertEquals(0, user.getUid()); +// assertEquals(0, user.getGid()); +// assertNotNull(user.getUserFullName()); +// assertNotNull(user.getHomeDirectory()); +// assertNotNull(user.getShell()); +// assertTrue(user.getShell().startsWith("/")); +// } +// +// @Test(groups = +// { "requires_unix" }) +// public void testDetectProcess() +// { +// assertTrue(Posix.canDetectProcesses()); +// assertTrue(Posix.isProcessRunningPS(Posix.getPid())); +// } +// +// 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 (Posix.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(); +// } +// } +// } + } -- GitLab