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