| /* |
| **** BEGIN LICENSE BLOCK ***** |
| * Version: EPL 2.0/GPL 2.0/LGPL 2.1 |
| * |
| * The contents of this file are subject to the Eclipse Public |
| * 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.eclipse.org/legal/cpl-v10.html |
| * |
| * Software distributed under the License is distributed on an "AS |
| * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or |
| * implied. See the License for the specific language governing |
| * rights and limitations under the License. |
| * |
| * Copyright (C) 2007 |
| * |
| * Alternatively, the contents of this file may be used under the terms of |
| * either of the GNU General Public License Version 2 or later (the "GPL"), |
| * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
| * in which case the provisions of the GPL or the LGPL are applicable instead |
| * of those above. If you wish to allow use of your version of this file only |
| * under the terms of either the GPL or the LGPL, and not to allow others to |
| * use your version of this file under the terms of the CPL, indicate your |
| * decision by deleting the provisions above and replace them with the notice |
| * and other provisions required by the GPL or the LGPL. If you do not delete |
| * the provisions above, a recipient may use your version of this file under |
| * the terms of any one of the CPL, the GPL or the LGPL. |
| ***** END LICENSE BLOCK *****/ |
| package jnr.posix; |
| |
| import static jnr.constants.platform.Errno.*; |
| |
| import java.io.*; |
| import java.lang.management.ManagementFactory; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.Channel; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.regex.Pattern; |
| |
| import jnr.constants.platform.Errno; |
| import jnr.posix.util.Chmod; |
| import jnr.posix.util.ExecIt; |
| import jnr.posix.util.JavaCrypt; |
| import jnr.posix.util.Platform; |
| |
| /** |
| * This libc implementation is created one per runtime instance versus the others which |
| * are expected to be one static instance for whole JVM. Because of this it is no big |
| * deal to make reference to a POSIXHandler directly. |
| */ |
| // FIXME: we ignore all exceptions with shell launcher...should we do something better |
| public class JavaLibCHelper { |
| public static final int STDIN = 0; |
| public static final int STDOUT = 1; |
| public static final int STDERR = 2; |
| |
| private static final ThreadLocal<Integer> errno = new ThreadLocal<Integer>(); |
| |
| private final POSIXHandler handler; |
| private final Map<String, String> env; |
| |
| |
| public JavaLibCHelper(POSIXHandler handler) { |
| this.env = new HashMap<String, String>(); |
| this.handler = handler; |
| } |
| |
| private static class ReflectiveAccess { |
| private static final Class SEL_CH_IMPL; |
| private static final Method SEL_CH_IMPL_GET_FD; |
| private static final Class FILE_CHANNEL_IMPL; |
| private static final Field FILE_CHANNEL_IMPL_FD; |
| private static final Field FILE_DESCRIPTOR_FD; |
| private static final Field FILE_DESCRIPTOR_HANDLE; |
| |
| static { |
| try { |
| Method getModule = Class.class.getMethod("getModule"); |
| Class<?> Module = Class.forName("java.lang.Module"); |
| Method isOpen = Module.getMethod("isOpen", String.class, Module); |
| Method isNamed = Module.getMethod("isNamed"); |
| Method addOpens = Module.getMethod("addOpens", String.class, Module); |
| Object JNRPosixModule = getModule.invoke(ReflectiveAccess.class); |
| Object JavaBaseModule = getModule.invoke(FileDescriptor.class); |
| |
| if (!((Boolean) isOpen.invoke(JavaBaseModule, "java.io", JNRPosixModule))) { |
| // warn that many APIs will be broken without module access |
| System.err.println("Some JDK modules may not be open to jnr-posix, which will break file descriptor and process APIs. See https://github.com/jnr/jnr-posix/wiki/Using-POSIX-with-Java-Modules"); |
| } else if (!((Boolean) isNamed.invoke(JNRPosixModule))) { |
| // explicitly open them to avoid the implicitly open warning |
| addOpens.invoke(JavaBaseModule, "java.io", JNRPosixModule); |
| addOpens.invoke(JavaBaseModule, "sun.nio.ch", JNRPosixModule); |
| } |
| } catch (Exception e) { |
| // ignore, we're not on Java 9+ |
| } |
| |
| Method getFD; |
| Class selChImpl; |
| try { |
| selChImpl = Class.forName("sun.nio.ch.SelChImpl"); |
| try { |
| getFD = selChImpl.getMethod("getFD"); |
| getFD.setAccessible(true); |
| } catch (Exception e) { |
| getFD = null; |
| } |
| } catch (Exception e) { |
| selChImpl = null; |
| getFD = null; |
| } |
| SEL_CH_IMPL = selChImpl; |
| SEL_CH_IMPL_GET_FD = getFD; |
| |
| Field fd; |
| Class fileChannelImpl; |
| try { |
| fileChannelImpl = Class.forName("sun.nio.ch.FileChannelImpl"); |
| try { |
| fd = fileChannelImpl.getDeclaredField("fd"); |
| fd.setAccessible(true); |
| } catch (Exception e) { |
| fd = null; |
| } |
| } catch (Exception e) { |
| fileChannelImpl = null; |
| fd = null; |
| } |
| FILE_CHANNEL_IMPL = fileChannelImpl; |
| FILE_CHANNEL_IMPL_FD = fd; |
| |
| Field ffd; |
| try { |
| ffd = FileDescriptor.class.getDeclaredField("fd"); |
| ffd.setAccessible(true); |
| } catch (Exception e) { |
| ffd = null; |
| } |
| FILE_DESCRIPTOR_FD = ffd; |
| |
| if (Platform.IS_WINDOWS) { |
| Field handle; |
| try { |
| handle = FileDescriptor.class.getDeclaredField("handle"); |
| handle.setAccessible(true); |
| } catch (Exception e) { |
| handle = null; |
| } |
| FILE_DESCRIPTOR_HANDLE = handle; |
| } else { |
| FILE_DESCRIPTOR_HANDLE = null; |
| } |
| } |
| } |
| |
| public static FileDescriptor getDescriptorFromChannel(Channel channel) { |
| if (ReflectiveAccess.SEL_CH_IMPL_GET_FD != null && ReflectiveAccess.SEL_CH_IMPL.isInstance(channel)) { |
| // Pipe Source and Sink, Sockets, and other several other selectable channels |
| try { |
| return (FileDescriptor)ReflectiveAccess.SEL_CH_IMPL_GET_FD.invoke(channel); |
| } catch (Exception e) { |
| // return bogus below |
| } |
| } else if (ReflectiveAccess.FILE_CHANNEL_IMPL_FD != null && ReflectiveAccess.FILE_CHANNEL_IMPL.isInstance(channel)) { |
| // FileChannels |
| try { |
| return (FileDescriptor)ReflectiveAccess.FILE_CHANNEL_IMPL_FD.get(channel); |
| } catch (Exception e) { |
| // return bogus below |
| } |
| } else if (ReflectiveAccess.FILE_DESCRIPTOR_FD != null) { |
| // anything else that implements a getFD method that returns an int |
| FileDescriptor unixFD = new FileDescriptor(); |
| |
| try { |
| Method getFD = channel.getClass().getMethod("getFD"); |
| ReflectiveAccess.FILE_DESCRIPTOR_FD.set(unixFD, (Integer)getFD.invoke(channel)); |
| return unixFD; |
| } catch (Exception e) { |
| // return bogus below |
| } |
| } |
| return new FileDescriptor(); |
| } |
| |
| static int errno() { |
| Integer errno = JavaLibCHelper.errno.get(); |
| return errno != null ? errno : 0; |
| } |
| |
| static void errno(int errno) { |
| JavaLibCHelper.errno.set(errno); |
| } |
| |
| static void errno(Errno errno) { |
| JavaLibCHelper.errno.set(errno.intValue()); |
| } |
| |
| public int chmod(String filename, int mode) { |
| return Chmod.chmod(new JavaSecuredFile(filename), Integer.toOctalString(mode)); |
| } |
| |
| public int chown(String filename, int user, int group) { |
| PosixExec launcher = new PosixExec(handler); |
| int chownResult = -1; |
| int chgrpResult = -1; |
| |
| try { |
| if (user != -1) chownResult = launcher.runAndWait("chown", "" + user, filename); |
| if (group != -1) chgrpResult = launcher.runAndWait("chgrp ", "" + user, filename); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| } catch (Exception e) { |
| } |
| |
| return chownResult != -1 && chgrpResult != -1 ? 0 : 1; |
| } |
| |
| public static CharSequence crypt(CharSequence original, CharSequence salt) { |
| return JavaCrypt.crypt(original, salt); |
| } |
| |
| // FIXME: This version has no idea what charset you want, so it just uses default. |
| public static byte[] crypt(byte[] original, byte[] salt) { |
| return JavaCrypt.crypt(new String(original), new String(salt)).toString().getBytes(); |
| } |
| |
| public int getfd(FileDescriptor descriptor) { |
| return getfdFromDescriptor(descriptor); |
| } |
| |
| public static int getfdFromDescriptor(FileDescriptor descriptor) { |
| if (descriptor == null || ReflectiveAccess.FILE_DESCRIPTOR_FD == null) return -1; |
| try { |
| return ReflectiveAccess.FILE_DESCRIPTOR_FD.getInt(descriptor); |
| } catch (SecurityException e) { |
| } catch (IllegalArgumentException e) { |
| } catch (IllegalAccessException e) { |
| } |
| |
| return -1; |
| } |
| |
| public static HANDLE gethandle(FileDescriptor descriptor) { |
| if (descriptor == null || ReflectiveAccess.FILE_DESCRIPTOR_HANDLE == null) return HANDLE.valueOf(-1); |
| try { |
| return gethandle(ReflectiveAccess.FILE_DESCRIPTOR_HANDLE.getLong(descriptor)); |
| } catch (SecurityException e) { |
| } catch (IllegalArgumentException e) { |
| } catch (IllegalAccessException e) { |
| } |
| |
| return HANDLE.valueOf(-1); |
| } |
| |
| public static HANDLE gethandle(long descriptor) { |
| return HANDLE.valueOf(descriptor); |
| } |
| |
| public String getlogin() { |
| return System.getProperty("user.name"); |
| } |
| |
| public String gethostname() { |
| String hn = System.getenv("HOSTNAME"); |
| if (hn == null) hn = System.getenv("COMPUTERNAME"); |
| return hn; |
| } |
| |
| public int getpid() { |
| try { |
| return handler.getPID(); |
| } catch (UnsupportedOperationException uoe) { |
| // if handler raises UOE, as our default handler does, try other ways |
| |
| // Java 9+ provide ProcessHandle.current |
| try { |
| Class processHandle = Class.forName("java.lang.ProcessHandle"); |
| Object current = processHandle.getMethod("current").invoke(null); // static |
| return (int) (long) (Long) processHandle.getMethod("pid").invoke(current); |
| } catch (Exception e) { |
| // ignore, try Java 8 logic below |
| } |
| |
| // Java 8- can use management beans to infer the pid |
| try { |
| String runtimeName = ManagementFactory.getRuntimeMXBean().getName(); |
| int index = runtimeName.indexOf('@'); |
| |
| if (index > 0) { |
| return (int) Long.parseLong(runtimeName.substring(0, index)); |
| } |
| } catch (Exception e) { |
| // ignore, rethrow UOE below |
| } |
| |
| // couldn't do it, rethrow |
| throw uoe; |
| } |
| |
| } |
| ThreadLocal<Integer> pwIndex = new ThreadLocal<Integer>() { |
| @Override |
| protected Integer initialValue() { |
| return 0; |
| } |
| }; |
| public Passwd getpwent() { |
| Passwd retVal = pwIndex.get().intValue() == 0 ? new JavaPasswd(handler) : null; |
| pwIndex.set(pwIndex.get() + 1); |
| return retVal; |
| } |
| |
| public int setpwent() { |
| return 0; |
| } |
| |
| public int endpwent() { |
| pwIndex.set(0); |
| return 0; |
| } |
| public Passwd getpwuid(int which) { |
| return which == JavaPOSIX.LoginInfo.UID ? new JavaPasswd(handler) : null; |
| } |
| public int isatty(int fd) { |
| return (fd == STDOUT || fd == STDIN || fd == STDERR) ? 1 : 0; |
| } |
| |
| public int link(String oldpath, String newpath) { |
| try { |
| return new PosixExec(handler).runAndWait("ln", oldpath, newpath); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| } catch (Exception e) { |
| } |
| errno(EINVAL); |
| return -1; // We tried and failed for some reason. Indicate error. |
| } |
| |
| public int lstat(String path, FileStat stat) { |
| File file = new JavaSecuredFile(path); |
| |
| if (!file.exists()) { |
| errno(ENOENT); |
| return -1; |
| } |
| |
| // FIXME: Bulletproof this or no? |
| JavaFileStat jstat = (JavaFileStat) stat; |
| |
| jstat.setup(path); |
| |
| // TODO: Add error reporting for cases we can calculate: ENOTDIR, ENAMETOOLONG, ENOENT |
| // EACCES, ELOOP, EFAULT, EIO |
| |
| return 0; |
| } |
| |
| public int mkdir(String path, int mode) { |
| File dir = new JavaSecuredFile(path); |
| |
| if (!dir.mkdir()) return -1; |
| |
| chmod(path, mode); |
| |
| return 0; |
| } |
| |
| public int rmdir(String path) { |
| return new JavaSecuredFile(path).delete() ? 0 : -1; |
| } |
| |
| public static int chdir(String path) { |
| System.setProperty("user.dir", path); |
| return 0; |
| } |
| |
| public int stat(String path, FileStat stat) { |
| // FIXME: Bulletproof this or no? |
| JavaFileStat jstat = (JavaFileStat) stat; |
| |
| try { |
| File file = new JavaSecuredFile(path); |
| |
| if (!file.exists()) { |
| errno(ENOENT); |
| return -1; |
| } |
| |
| jstat.setup(file.getCanonicalPath()); |
| } catch (IOException e) { |
| // TODO: Throw error when we have problems stat'ing canonicalizing |
| } |
| |
| // TODO: Add error reporting for cases we can calculate: ENOTDIR, ENAMETOOLONG, |
| // EACCES, ELOOP, EFAULT, EIO |
| |
| return 0; |
| } |
| |
| public int symlink(String oldpath, String newpath) { |
| try { |
| return new PosixExec(handler).runAndWait("ln", "-s", oldpath, newpath); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| } catch (Exception e) { |
| } |
| errno(EEXIST); |
| return -1; // We tried and failed for some reason. Indicate error. |
| |
| } |
| |
| public int readlink(String oldpath, ByteBuffer buffer, int length) throws IOException { |
| try { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| new PosixExec(handler).runAndWait(baos, "readlink", oldpath); |
| |
| byte[] bytes = baos.toByteArray(); |
| |
| if (bytes.length > length || bytes.length == 0) return -1; |
| buffer.put(bytes, 0, bytes.length - 1); // trim off \n |
| |
| |
| return buffer.position(); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| } |
| errno(ENOENT); |
| return -1; // We tried and failed for some reason. Indicate error. |
| } |
| |
| public Map<String, String> getEnv() { |
| return env; |
| } |
| |
| public static FileDescriptor toFileDescriptor(int fileDescriptor) { |
| FileDescriptor descriptor = new FileDescriptor(); |
| try { |
| ReflectiveAccess.FILE_DESCRIPTOR_FD.set(descriptor, fileDescriptor); |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException(e); |
| } |
| return descriptor; |
| } |
| |
| public static FileDescriptor toFileDescriptor(HANDLE fileDescriptor) { |
| FileDescriptor descriptor = new FileDescriptor(); |
| try { |
| ReflectiveAccess.FILE_DESCRIPTOR_HANDLE.set(descriptor, fileDescriptor.toPointer().address()); |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException(e); |
| } |
| return descriptor; |
| } |
| |
| private static class PosixExec extends ExecIt { |
| private final AtomicReference<Errno> errno = new AtomicReference<Errno>(Errno.EINVAL); |
| private final ErrnoParsingOutputStream errorStream = new ErrnoParsingOutputStream(errno); |
| |
| public PosixExec(POSIXHandler handler) { |
| super(handler); |
| } |
| |
| private int parseResult(int result) { |
| if (result == 0) { |
| return result; |
| } |
| errno(errno.get()); |
| return -1; |
| } |
| |
| public int runAndWait(String... args) throws IOException, InterruptedException { |
| return runAndWait(handler.getOutputStream(), errorStream, args); |
| } |
| |
| public int runAndWait(OutputStream output, String... args) throws IOException, InterruptedException { |
| return runAndWait(output, errorStream, args); |
| } |
| |
| public int runAndWait(OutputStream output, OutputStream error, String... args) throws IOException, InterruptedException { |
| return parseResult(super.runAndWait(output, error, args)); |
| } |
| } |
| |
| private static final class ErrnoParsingOutputStream extends OutputStream { |
| private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| private final AtomicReference<Errno> errno; |
| |
| private ErrnoParsingOutputStream(AtomicReference<Errno> errno) { |
| this.errno = errno; |
| } |
| |
| @Override |
| public void write(int b) throws IOException { |
| if (b != '\r' && b != '\n' && b != -1) { |
| baos.write(b); |
| } else if (baos.size() > 0) { |
| String errorString = baos.toString(); |
| baos.reset(); |
| parseError(errorString); |
| } |
| } |
| |
| static Map<Pattern, Errno> errorPatterns = new HashMap<Pattern, Errno>(); |
| static { |
| errorPatterns.put(Pattern.compile("File exists"), Errno.EEXIST); |
| errorPatterns.put(Pattern.compile("Operation not permitted"), Errno.EPERM); |
| errorPatterns.put(Pattern.compile("No such file or directory"), Errno.ENOENT); |
| errorPatterns.put(Pattern.compile("Input/output error"), Errno.EIO); |
| errorPatterns.put(Pattern.compile("Not a directory"), Errno.ENOTDIR); |
| errorPatterns.put(Pattern.compile("No space left on device"), Errno.ENOSPC); |
| errorPatterns.put(Pattern.compile("Read-only file system"), Errno.EROFS); |
| errorPatterns.put(Pattern.compile("Too many links"), Errno.EMLINK); |
| } |
| |
| void parseError(String errorString) { |
| for (Map.Entry<Pattern, Errno> entry : errorPatterns.entrySet()) { |
| if (entry.getKey().matcher(errorString).find()) { |
| errno.set(entry.getValue()); |
| } |
| } |
| } |
| } |
| } |