From b23f0a08bf2ebcba8997afe965af9769cd19a2bd Mon Sep 17 00:00:00 2001
From: stepan <stepan.sindelar@oracle.com>
Date: Tue, 20 Feb 2018 16:37:31 +0100
Subject: [PATCH] Implement mkdir and chmod in Java

---
 .../managed/Managed_DownCallNodeFactory.java  | 139 +++++++++++++++++-
 .../truffle/r/library/tools/DirChmod.java     |   8 +-
 .../r/nodes/builtin/base/FileFunctions.java   |   6 +-
 .../r/nodes/builtin/base/SysFunctions.java    |   7 +-
 .../truffle/r/runtime/FileSystemUtils.java    |  67 +++++++++
 .../truffle/r/runtime/ffi/BaseRFFI.java       |  46 ------
 .../truffle/r/runtime/ffi/NativeFunction.java |   2 -
 7 files changed, 211 insertions(+), 64 deletions(-)
 create mode 100644 com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/FileSystemUtils.java

diff --git a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_DownCallNodeFactory.java b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_DownCallNodeFactory.java
index 6d2a509f84..c30daa85b3 100644
--- a/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_DownCallNodeFactory.java
+++ b/com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/managed/Managed_DownCallNodeFactory.java
@@ -22,11 +22,30 @@
  */
 package com.oracle.truffle.r.ffi.impl.managed;
 
+import java.io.IOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import com.oracle.truffle.api.CallTarget;
+import com.oracle.truffle.api.Truffle;
+import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.interop.ForeignAccess;
+import com.oracle.truffle.api.interop.ForeignAccess.StandardFactory;
 import com.oracle.truffle.api.interop.TruffleObject;
-import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.api.nodes.RootNode;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RError.Message;
 import com.oracle.truffle.r.runtime.ffi.DownCallNodeFactory;
 import com.oracle.truffle.r.runtime.ffi.NativeFunction;
+import com.oracle.truffle.r.runtime.ffi.interop.NativeCharArray;
 
 public final class Managed_DownCallNodeFactory extends DownCallNodeFactory {
 
@@ -40,14 +59,23 @@ public final class Managed_DownCallNodeFactory extends DownCallNodeFactory {
         return new DownCallNode(function) {
             @Override
             protected TruffleObject getTarget(NativeFunction function) {
+                if (function == NativeFunction.getpid) {
+                    return new GetPID();
+                } else if (function == NativeFunction.mkdtemp) {
+                    return new Mkdtemp();
+                } else if (function == NativeFunction.getcwd) {
+                    return new Getwd();
+                }
                 return new DummyFunctionObject(function);
             }
 
             @Override
             protected void wrapArguments(TruffleObject function, Object[] args) {
                 // Report unsupported functions at invocation time
-                assert function instanceof DummyFunctionObject;
-                throw Managed_RFFIFactory.unsupported(((DummyFunctionObject) function).function.getCallName());
+                if (function instanceof DummyFunctionObject) {
+                    throw Managed_RFFIFactory.unsupported(((DummyFunctionObject) function).function.getCallName());
+                }
+                return 0;
             }
 
             @Override
@@ -69,4 +97,109 @@ public final class Managed_DownCallNodeFactory extends DownCallNodeFactory {
             return null;
         }
     }
+
+    // PID is used as a seed for random numbers generation. We still want to support random numbers
+    // in managed mode so we make up some (random) value
+    private static final class GetPID implements TruffleObject {
+        private static final int fakePid = (int) System.currentTimeMillis();
+
+        @Override
+        public ForeignAccess getForeignAccess() {
+            return ForeignAccess.create(GetPID.class, new StandardFactory() {
+                @Override
+                public CallTarget accessIsExecutable() {
+                    return Truffle.getRuntime().createCallTarget(RootNode.createConstantNode(true));
+                }
+
+                @Override
+                public CallTarget accessExecute(int argumentsLength) {
+                    return Truffle.getRuntime().createCallTarget(RootNode.createConstantNode(fakePid));
+                }
+            });
+        }
+    }
+
+    /**
+     * Implements simplified version of the {@code mkdtemp} from {@code stdlib}. The reason why we
+     * do not use only Java version is that the real {@code mkdtemp} seems to be more reliable and
+     * secure.
+     */
+    private static final class Mkdtemp implements TruffleObject {
+        private static final FileAttribute<Set<PosixFilePermission>> irwxuPermissions = PosixFilePermissions.asFileAttribute(
+                        EnumSet.of(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
+
+        @Override
+        public ForeignAccess getForeignAccess() {
+            return ForeignAccess.create(GetPID.class, new StandardFactory() {
+                @Override
+                public CallTarget accessIsExecutable() {
+                    return Truffle.getRuntime().createCallTarget(RootNode.createConstantNode(true));
+                }
+
+                @Override
+                public CallTarget accessExecute(int argumentsLength) {
+                    return Truffle.getRuntime().createCallTarget(new RootNode(null) {
+                        @Override
+                        public Object execute(VirtualFrame frame) {
+                            NativeCharArray templateBytes = (NativeCharArray) ForeignAccess.getArguments(frame).get(0);
+                            String template = new String(templateBytes.getValue(), 0, templateBytes.getValue().length - 1);
+                            if (!template.endsWith("XXXXXX")) {
+                                throw new IllegalArgumentException("template must end with XXXXXX");
+                            }
+                            String templatePrefix = template.substring(0, template.length() - 6);
+                            Path path = null;
+                            int counter = 0;
+                            boolean done = false;
+                            while (!done) {
+                                try {
+                                    path = Paths.get(templatePrefix + String.format("%06d", counter++));
+                                    if (Files.exists(path)) {
+                                        continue;
+                                    }
+                                    Files.createDirectories(path, irwxuPermissions);
+                                    done = true;
+                                } catch (FileAlreadyExistsException e) {
+                                    // nop
+                                } catch (IOException e) {
+                                    throw RError.error(RError.NO_CALLER, Message.GENERIC, "Cannot create temp directories.");
+                                }
+                            }
+                            byte[] resultBytes = path.toString().getBytes();
+                            System.arraycopy(resultBytes, 0, templateBytes.getValue(), 0, Math.min(resultBytes.length, templateBytes.getValue().length));
+                            return 1;
+                        }
+                    });
+                }
+            });
+        }
+    }
+
+    /**
+     * Gives the current working directory. For some reasons, this is not exactly equivalent to
+     * calling the C function, which manifests itself during codetools package installation.
+     */
+    private static final class Getwd implements TruffleObject {
+        @Override
+        public ForeignAccess getForeignAccess() {
+            return ForeignAccess.create(GetPID.class, new StandardFactory() {
+                @Override
+                public CallTarget accessIsExecutable() {
+                    return Truffle.getRuntime().createCallTarget(RootNode.createConstantNode(true));
+                }
+
+                @Override
+                public CallTarget accessExecute(int argumentsLength) {
+                    return Truffle.getRuntime().createCallTarget(new RootNode(null) {
+                        @Override
+                        public Object execute(VirtualFrame frame) {
+                            NativeCharArray buffer = (NativeCharArray) ForeignAccess.getArguments(frame).get(0);
+                            byte[] bytes = Paths.get(".").toAbsolutePath().normalize().toString().getBytes();
+                            System.arraycopy(bytes, 0, buffer.getValue(), 0, Math.min(bytes.length, buffer.getValue().length));
+                            return 1;
+                        }
+                    });
+                }
+            });
+        }
+    }
 }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/tools/DirChmod.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/tools/DirChmod.java
index 38bc4c0ad2..3622ed37c1 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/tools/DirChmod.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/tools/DirChmod.java
@@ -5,7 +5,7 @@
  *
  * Copyright (c) 1995-2015, The R Core Team
  * Copyright (c) 2003, The R Foundation
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates
  *
  * All rights reserved.
  */
@@ -27,11 +27,11 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.dsl.Fallback;
 import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
+import com.oracle.truffle.r.runtime.FileSystemUtils;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.Utils;
 import com.oracle.truffle.r.runtime.data.RNull;
-import com.oracle.truffle.r.runtime.ffi.BaseRFFI;
 
 public abstract class DirChmod extends RExternalBuiltinNode.Arg2 {
 
@@ -46,8 +46,6 @@ public abstract class DirChmod extends RExternalBuiltinNode.Arg2 {
         casts.arg(1).asLogicalVector().findFirst(RRuntime.LOGICAL_FALSE).map(toBoolean());
     }
 
-    @Child private BaseRFFI.ChmodNode chmodNode = BaseRFFI.ChmodNode.create();
-
     @Specialization
     @TruffleBoundary
     protected RNull dirChmod(String pathName, boolean setGroupWrite) {
@@ -69,7 +67,7 @@ public abstract class DirChmod extends RExternalBuiltinNode.Arg2 {
                 int elementMode = Utils.intFilePermissions(pfa.permissions());
                 int newMode = Files.isDirectory(element) ? elementMode | dirMask : elementMode | fileMask;
                 // System.out.printf("path %s: old %o, new %o%n", element, elementMode, newMode);
-                chmodNode.execute(element.toString(), newMode);
+                FileSystemUtils.chmod(element.toString(), newMode);
             }
         } catch (IOException ex) {
             // ignore
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FileFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FileFunctions.java
index 8dacc87f84..45461997df 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FileFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FileFunctions.java
@@ -63,6 +63,7 @@ import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.SetClass
 import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 import com.oracle.truffle.r.nodes.unary.CastStringNode;
 import com.oracle.truffle.r.nodes.unary.CastStringNodeGen;
+import com.oracle.truffle.r.runtime.FileSystemUtils;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RError.Message;
 import com.oracle.truffle.r.runtime.RInternalError;
@@ -81,7 +82,6 @@ import com.oracle.truffle.r.runtime.data.RSymbol;
 import com.oracle.truffle.r.runtime.data.RTypedValue;
 import com.oracle.truffle.r.runtime.data.model.RAbstractContainer;
 import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
-import com.oracle.truffle.r.runtime.ffi.BaseRFFI;
 
 // Much of this code was influences/transcribed from GnuR src/main/platform.c
 
@@ -1210,8 +1210,6 @@ public class FileFunctions {
             casts.arg("mode").asIntegerVector().findFirst().mapIf(intNA(), constant(0777));
         }
 
-        @Child private BaseRFFI.MkdirNode mkdirNode = BaseRFFI.MkdirNode.create();
-
         @Specialization
         @TruffleBoundary
         protected byte dirCreate(String pathIn, boolean showWarnings, boolean recursive, int octMode) {
@@ -1247,7 +1245,7 @@ public class FileFunctions {
 
         private boolean mkdir(String path, boolean showWarnings, int mode) {
             try {
-                mkdirNode.execute(path, mode);
+                FileSystemUtils.mkdir(path, mode);
                 return true;
             } catch (IOException ex) {
                 if (showWarnings) {
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/SysFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/SysFunctions.java
index 27f194f7e9..985bc810a8 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/SysFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/SysFunctions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -53,6 +53,7 @@ import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinPackages;
+import com.oracle.truffle.r.runtime.FileSystemUtils;
 import com.oracle.truffle.r.runtime.RArguments;
 import com.oracle.truffle.r.runtime.RCaller;
 import com.oracle.truffle.r.runtime.REnvVars;
@@ -300,8 +301,6 @@ public class SysFunctions {
             casts.arg("use_umask").asLogicalVector().findFirst().mustNotBeNA().map(toBoolean());
         }
 
-        @Child private BaseRFFI.ChmodNode chmodNode = BaseRFFI.ChmodNode.create();
-
         @Specialization
         @TruffleBoundary
         protected RLogicalVector sysChmod(RAbstractStringVector pathVec, RAbstractIntVector octmode, @SuppressWarnings("unused") boolean useUmask) {
@@ -311,7 +310,7 @@ public class SysFunctions {
                 if (path.length() == 0 || RRuntime.isNA(path)) {
                     continue;
                 }
-                int result = chmodNode.execute(path, octmode.getDataAt(i % octmode.getLength()));
+                int result = FileSystemUtils.chmod(path, octmode.getDataAt(i % octmode.getLength()));
                 data[i] = RRuntime.asLogical(result == 0);
             }
             return RDataFactory.createLogicalVector(data, RDataFactory.COMPLETE_VECTOR);
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/FileSystemUtils.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/FileSystemUtils.java
new file mode 100644
index 0000000000..8d9a196593
--- /dev/null
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/FileSystemUtils.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.runtime;
+
+import java.io.IOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.EnumSet;
+import java.util.Set;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.r.runtime.RError.Message;
+
+public class FileSystemUtils {
+    private static PosixFilePermission[] permissionValues = PosixFilePermission.values();
+
+    private static Set<PosixFilePermission> permissionsFromMode(int mode) {
+        Set<PosixFilePermission> permissions = EnumSet.noneOf(PosixFilePermission.class);
+        for (int i = 0; i < permissionValues.length; i++) {
+            if ((mode & (1 << (permissionValues.length - i - 1))) != 0) {
+                permissions.add(permissionValues[i]);
+            }
+        }
+        return permissions;
+    }
+
+    @TruffleBoundary
+    public static int chmod(String path, int mode) {
+        try {
+            Files.setPosixFilePermissions(Paths.get(path), permissionsFromMode(mode));
+            return mode;
+        } catch (IOException e) {
+            throw RError.error(RError.NO_CALLER, Message.GENERIC, "Cannot change file permissions.");
+        }
+    }
+
+    @TruffleBoundary
+    public static void mkdir(String dir, int mode) throws IOException {
+        Set<PosixFilePermission> permissions = permissionsFromMode(mode);
+        Files.createDirectory(Paths.get(dir), PosixFilePermissions.asFileAttribute(permissions));
+    }
+}
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/BaseRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/BaseRFFI.java
index 297d4c7b6d..a658970712 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/BaseRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/BaseRFFI.java
@@ -109,26 +109,6 @@ public final class BaseRFFI {
         }
     }
 
-    public static final class MkdirNode extends NativeCallNode {
-
-        private MkdirNode(DownCallNodeFactory parent) {
-            super(parent.createDownCallNode(NativeFunction.mkdir));
-        }
-
-        /**
-         * Create directory with given mode. Exception is thrown on error.
-         */
-        public void execute(String dir, int mode) throws IOException {
-            if ((int) call(dir, mode) != 0) {
-                throw new IOException("mkdir " + dir + " failed");
-            }
-        }
-
-        public static MkdirNode create() {
-            return RFFIFactory.getBaseRFFI().createMkdirNode();
-        }
-    }
-
     public static final class ReadlinkNode extends NativeCallNode {
         private static final int EINVAL = 22;
 
@@ -192,24 +172,6 @@ public final class BaseRFFI {
         }
     }
 
-    public static final class ChmodNode extends NativeCallNode {
-
-        private ChmodNode(DownCallNodeFactory parent) {
-            super(parent.createDownCallNode(NativeFunction.chmod));
-        }
-
-        /**
-         * Change the file mode of {@code path}.
-         */
-        public int execute(String path, int mode) {
-            return (int) call(path, mode);
-        }
-
-        public static ChmodNode create() {
-            return RFFIFactory.getBaseRFFI().createChmodNode();
-        }
-    }
-
     public static final class StrtolNode extends NativeCallNode {
 
         private StrtolNode(DownCallNodeFactory parent) {
@@ -317,10 +279,6 @@ public final class BaseRFFI {
         return new SetwdNode(downCallNodeFactory);
     }
 
-    public MkdirNode createMkdirNode() {
-        return new MkdirNode(downCallNodeFactory);
-    }
-
     public ReadlinkNode createReadlinkNode() {
         return new ReadlinkNode(downCallNodeFactory);
     }
@@ -329,10 +287,6 @@ public final class BaseRFFI {
         return new MkdtempNode(downCallNodeFactory);
     }
 
-    public ChmodNode createChmodNode() {
-        return new ChmodNode(downCallNodeFactory);
-    }
-
     public StrtolNode createStrtolNode() {
         return new StrtolNode(downCallNodeFactory);
     }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/NativeFunction.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/NativeFunction.java
index f30a5dfbf4..ba0908f9ef 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/NativeFunction.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/NativeFunction.java
@@ -36,10 +36,8 @@ public enum NativeFunction {
     getpid("(): sint32", "call_base_"),
     getcwd("([uint8], sint32): sint32", "call_base_"),
     chdir("(string): sint32", "call_base_"),
-    mkdir("(string, sint32): sint32", "call_base_"),
     readlink("((string, sint32): void, string): void", "call_base_"),
     mkdtemp("([uint8]): sint32", "call_base_"),
-    chmod("(string, sint32): sint32", "call_base_"),
     strtol("((sint64, sint32): void, string, sint32): void", "call_base_"),
     uname("((string, string, string, string, string): void): void", "call_base_"),
     glob("((string): void, string): void", "call_base_"),
-- 
GitLab