/src/org/jruby/RubyFile.java
Java | 1880 lines | 1364 code | 261 blank | 255 comment | 354 complexity | 7fdd9f30c60493e9347eb80ef2f20f89 MD5 | raw file
Possible License(s): GPL-3.0, JSON
- /*
- ***** BEGIN LICENSE BLOCK *****
- * Version: CPL 1.0/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Common Public
- * License Version 1.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) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
- * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
- * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
- * Copyright (C) 2003 Joey Gibson <joey@joeygibson.com>
- * Copyright (C) 2004-2007 Thomas E Enebo <enebo@acm.org>
- * Copyright (C) 2004-2007 Charles O Nutter <headius@headius.com>
- * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
- * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
- *
- * 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 org.jruby;
- import com.kenai.constantine.platform.OpenFlags;
- import org.jcodings.Encoding;
- import org.jruby.util.io.OpenFile;
- import org.jruby.util.io.ChannelDescriptor;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.Reader;
- import java.net.URI;
- import java.net.URL;
- import java.nio.channels.Channels;
- import java.nio.channels.FileChannel;
- import java.nio.channels.FileLock;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import java.util.zip.ZipEntry;
- import java.util.zip.ZipFile;
- import org.jruby.anno.JRubyClass;
- import org.jruby.anno.JRubyMethod;
- import org.jruby.anno.JRubyModule;
- import org.jruby.ext.posix.FileStat;
- import org.jruby.ext.posix.util.Platform;
- import org.jruby.runtime.Block;
- import org.jruby.runtime.ClassIndex;
- import org.jruby.runtime.ObjectAllocator;
- import org.jruby.runtime.ThreadContext;
- import static org.jruby.runtime.Visibility.*;
- import org.jruby.runtime.builtin.IRubyObject;
- import org.jruby.runtime.encoding.EncodingCapable;
- import org.jruby.util.ByteList;
- import org.jruby.util.io.DirectoryAsFileException;
- import org.jruby.util.io.PermissionDeniedException;
- import org.jruby.util.io.Stream;
- import org.jruby.util.io.ChannelStream;
- import org.jruby.util.io.ModeFlags;
- import org.jruby.util.JRubyFile;
- import org.jruby.util.TypeConverter;
- import org.jruby.util.io.BadDescriptorException;
- import org.jruby.util.io.FileExistsException;
- import org.jruby.util.io.InvalidValueException;
- import org.jruby.util.io.PipeException;
- import static org.jruby.CompatVersion.*;
- /**
- * Ruby File class equivalent in java.
- **/
- @JRubyClass(name="File", parent="IO", include="FileTest")
- public class RubyFile extends RubyIO implements EncodingCapable {
- private static final long serialVersionUID = 1L;
-
- public static final int LOCK_SH = 1;
- public static final int LOCK_EX = 2;
- public static final int LOCK_NB = 4;
- public static final int LOCK_UN = 8;
- private static final int FNM_NOESCAPE = 1;
- private static final int FNM_PATHNAME = 2;
- private static final int FNM_DOTMATCH = 4;
- private static final int FNM_CASEFOLD = 8;
- private static final int FNM_SYSCASE;
- private static int _cachedUmask = 0;
- private static final Object _umaskLock = new Object();
- static {
- if (Platform.IS_WINDOWS) {
- FNM_SYSCASE = FNM_CASEFOLD;
- } else {
- FNM_SYSCASE = 0;
- }
- }
- public Encoding getEncoding() {
- return null;
- }
- public void setEncoding(Encoding encoding) {
- // :)
- }
- private static boolean startsWithDriveLetterOnWindows(String path) {
- return (path != null)
- && Platform.IS_WINDOWS &&
- ((path.length()>1 && path.charAt(0) == '/') ?
- (path.length() > 2
- && isWindowsDriveLetter(path.charAt(1))
- && path.charAt(2) == ':') :
- (path.length() > 1
- && isWindowsDriveLetter(path.charAt(0))
- && path.charAt(1) == ':'));
- }
- // adjusts paths started with '/' or '\\', on windows.
- static String adjustRootPathOnWindows(Ruby runtime, String path, String dir) {
- if (path == null || !Platform.IS_WINDOWS) return path;
- // MRI behavior on Windows: it treats '/' as a root of
- // a current drive (but only if SINGLE slash is present!):
- // E.g., if current work directory is
- // 'D:/home/directory', then '/' means 'D:/'.
- //
- // Basically, '/path' is treated as a *RELATIVE* path,
- // relative to the current drive. '//path' is treated
- // as absolute one.
- if ((path.startsWith("/") && !(path.length() > 2 && path.charAt(2) == ':')) || path.startsWith("\\")) {
- if (path.length() > 1 && (path.charAt(1) == '/' || path.charAt(1) == '\\')) {
- return path;
- }
- // First try to use drive letter from supplied dir value,
- // then try current work dir.
- if (!startsWithDriveLetterOnWindows(dir)) {
- dir = runtime.getCurrentDirectory();
- }
- if (dir.length() >= 2) {
- path = dir.substring(0, 2) + path;
- }
- } else if (startsWithDriveLetterOnWindows(path) && path.length() == 2) {
- // compensate for missing slash after drive letter on windows
- path += "/";
- }
- return path;
- }
- protected String path;
- private FileLock currentLock;
-
- public RubyFile(Ruby runtime, RubyClass type) {
- super(runtime, type);
- }
-
- // XXX This constructor is a hack to implement the __END__ syntax.
- // Converting a reader back into an InputStream doesn't generally work.
- public RubyFile(Ruby runtime, String path, final Reader reader) {
- this(runtime, path, new InputStream() {
- public int read() throws IOException {
- return reader.read();
- }
- });
- }
-
- public RubyFile(Ruby runtime, String path, InputStream in) {
- super(runtime, runtime.getFile());
- this.path = path;
- try {
- this.openFile.setMainStream(ChannelStream.open(runtime, new ChannelDescriptor(Channels.newChannel(in))));
- } catch (InvalidValueException ex) {
- throw runtime.newErrnoEINVALError();
- }
- this.openFile.setMode(openFile.getMainStream().getModes().getOpenFileFlags());
- }
- private static ObjectAllocator FILE_ALLOCATOR = new ObjectAllocator() {
- public IRubyObject allocate(Ruby runtime, RubyClass klass) {
- RubyFile instance = new RubyFile(runtime, klass);
-
- instance.setMetaClass(klass);
-
- return instance;
- }
- };
- public String getPath() {
- return path;
- }
- @JRubyModule(name="File::Constants")
- public static class Constants {}
-
- public static RubyClass createFileClass(Ruby runtime) {
- RubyClass fileClass = runtime.defineClass("File", runtime.getIO(), FILE_ALLOCATOR);
- // Create Constants class
- RubyModule constants = fileClass.defineModuleUnder("Constants");
- runtime.setFile(fileClass);
- fileClass.index = ClassIndex.FILE;
- fileClass.setReifiedClass(RubyFile.class);
- RubyString separator = runtime.newString("/");
- ThreadContext context = runtime.getCurrentContext();
-
- fileClass.kindOf = new RubyModule.KindOf() {
- @Override
- public boolean isKindOf(IRubyObject obj, RubyModule type) {
- return obj instanceof RubyFile;
- }
- };
- separator.freeze(context);
- fileClass.defineConstant("SEPARATOR", separator);
- fileClass.defineConstant("Separator", separator);
-
- if (File.separatorChar == '\\') {
- RubyString altSeparator = runtime.newString("\\");
- altSeparator.freeze(context);
- fileClass.defineConstant("ALT_SEPARATOR", altSeparator);
- } else {
- fileClass.defineConstant("ALT_SEPARATOR", runtime.getNil());
- }
-
- RubyString pathSeparator = runtime.newString(File.pathSeparator);
- pathSeparator.freeze(context);
- fileClass.defineConstant("PATH_SEPARATOR", pathSeparator);
- // TODO: why are we duplicating the constants here, and then in
- // File::Constants below? File::Constants is included in IO.
- // TODO: These were missing, so we're not handling them elsewhere?
- fileClass.fastSetConstant("FNM_NOESCAPE", runtime.newFixnum(FNM_NOESCAPE));
- fileClass.fastSetConstant("FNM_CASEFOLD", runtime.newFixnum(FNM_CASEFOLD));
- fileClass.fastSetConstant("FNM_SYSCASE", runtime.newFixnum(FNM_SYSCASE));
- fileClass.fastSetConstant("FNM_DOTMATCH", runtime.newFixnum(FNM_DOTMATCH));
- fileClass.fastSetConstant("FNM_PATHNAME", runtime.newFixnum(FNM_PATHNAME));
-
- // Create constants for open flags
- for (OpenFlags f : OpenFlags.values()) {
- // Strip off the O_ prefix, so they become File::RDONLY, and so on
- final String name = f.name();
- if (name.startsWith("O_")) {
- final String cname = name.substring(2);
- // Special case for handling ACCMODE, since constantine will generate
- // an invalid value if it is not defined by the platform.
- final RubyFixnum cvalue = f == OpenFlags.O_ACCMODE
- ? runtime.newFixnum(ModeFlags.ACCMODE)
- : runtime.newFixnum(f.value());
- fileClass.fastSetConstant(cname, cvalue);
- constants.fastSetConstant(cname, cvalue);
- }
- }
-
- // Create constants for flock
- fileClass.fastSetConstant("LOCK_SH", runtime.newFixnum(RubyFile.LOCK_SH));
- fileClass.fastSetConstant("LOCK_EX", runtime.newFixnum(RubyFile.LOCK_EX));
- fileClass.fastSetConstant("LOCK_NB", runtime.newFixnum(RubyFile.LOCK_NB));
- fileClass.fastSetConstant("LOCK_UN", runtime.newFixnum(RubyFile.LOCK_UN));
-
- constants.fastSetConstant("FNM_NOESCAPE", runtime.newFixnum(FNM_NOESCAPE));
- constants.fastSetConstant("FNM_CASEFOLD", runtime.newFixnum(FNM_CASEFOLD));
- constants.fastSetConstant("FNM_SYSCASE", runtime.newFixnum(FNM_SYSCASE));
- constants.fastSetConstant("FNM_DOTMATCH", runtime.newFixnum(FNM_DOTMATCH));
- constants.fastSetConstant("FNM_PATHNAME", runtime.newFixnum(FNM_PATHNAME));
-
- // Create constants for flock
- constants.fastSetConstant("LOCK_SH", runtime.newFixnum(RubyFile.LOCK_SH));
- constants.fastSetConstant("LOCK_EX", runtime.newFixnum(RubyFile.LOCK_EX));
- constants.fastSetConstant("LOCK_NB", runtime.newFixnum(RubyFile.LOCK_NB));
- constants.fastSetConstant("LOCK_UN", runtime.newFixnum(RubyFile.LOCK_UN));
-
- // File::Constants module is included in IO.
- runtime.getIO().includeModule(constants);
- runtime.getFileTest().extend_object(fileClass);
-
- fileClass.defineAnnotatedMethods(RubyFile.class);
- // For JRUBY-5276, physically define FileTest methods on File's singleton
- fileClass.getSingletonClass().defineAnnotatedMethods(RubyFileTest.FileTestFileMethods.class);
-
- return fileClass;
- }
-
- @JRubyMethod
- @Override
- public IRubyObject close() {
- // Make sure any existing lock is released before we try and close the file
- if (currentLock != null) {
- try {
- currentLock.release();
- } catch (IOException e) {
- throw getRuntime().newIOError(e.getMessage());
- }
- }
- return super.close();
- }
- @JRubyMethod(required = 1)
- public IRubyObject flock(ThreadContext context, IRubyObject lockingConstant) {
- // TODO: port exact behavior from MRI, and move most locking logic into ChannelDescriptor
- // TODO: for all LOCK_NB cases, return false if they would block
- ChannelDescriptor descriptor = openFile.getMainStream().getDescriptor();
-
- // null channel always succeeds for all locking operations
- if (descriptor.isNull()) return RubyFixnum.zero(context.getRuntime());
- if (descriptor.getChannel() instanceof FileChannel) {
- FileChannel fileChannel = (FileChannel)descriptor.getChannel();
- int lockMode = RubyNumeric.num2int(lockingConstant);
- // Exclusive locks in Java require the channel to be writable, otherwise
- // an exception is thrown (terminating JRuby execution).
- // But flock behavior of MRI is that it allows
- // exclusive locks even on non-writable file. So we convert exclusive
- // lock to shared lock if the channel is not writable, to better match
- // the MRI behavior.
- if (!openFile.isWritable() && (lockMode & LOCK_EX) > 0) {
- lockMode = (lockMode ^ LOCK_EX) | LOCK_SH;
- }
- try {
- switch (lockMode) {
- case LOCK_UN:
- case LOCK_UN | LOCK_NB:
- if (currentLock != null) {
- currentLock.release();
- currentLock = null;
- return RubyFixnum.zero(context.getRuntime());
- }
- break;
- case LOCK_EX:
- if (currentLock != null) {
- currentLock.release();
- currentLock = null;
- }
- currentLock = fileChannel.lock();
- if (currentLock != null) {
- return RubyFixnum.zero(context.getRuntime());
- }
- break;
- case LOCK_EX | LOCK_NB:
- if (currentLock != null) {
- currentLock.release();
- currentLock = null;
- }
- currentLock = fileChannel.tryLock();
- if (currentLock != null) {
- return RubyFixnum.zero(context.getRuntime());
- }
- break;
- case LOCK_SH:
- if (currentLock != null) {
- currentLock.release();
- currentLock = null;
- }
- currentLock = fileChannel.lock(0L, Long.MAX_VALUE, true);
- if (currentLock != null) {
- return RubyFixnum.zero(context.getRuntime());
- }
- break;
- case LOCK_SH | LOCK_NB:
- if (currentLock != null) {
- currentLock.release();
- currentLock = null;
- }
- currentLock = fileChannel.tryLock(0L, Long.MAX_VALUE, true);
- if (currentLock != null) {
- return RubyFixnum.zero(context.getRuntime());
- }
- break;
- default:
- }
- } catch (IOException ioe) {
- if (context.getRuntime().getDebug().isTrue()) {
- ioe.printStackTrace(System.err);
- }
- } catch (java.nio.channels.OverlappingFileLockException ioe) {
- if (context.getRuntime().getDebug().isTrue()) {
- ioe.printStackTrace(System.err);
- }
- }
- return (lockMode & LOCK_EX) == 0 ? RubyFixnum.zero(context.getRuntime()) : context.getRuntime().getFalse();
- } else {
- // We're not actually a real file, so we can't flock
- return context.getRuntime().getFalse();
- }
- }
- @JRubyMethod(required = 1, optional = 2, visibility = PRIVATE, compat = RUBY1_8)
- @Override
- public IRubyObject initialize(IRubyObject[] args, Block block) {
- if (openFile == null) {
- throw getRuntime().newRuntimeError("reinitializing File");
- }
-
- if (args.length > 0 && args.length < 3) {
- IRubyObject fd = TypeConverter.convertToTypeWithCheck(args[0], getRuntime().getFixnum(), "to_int");
- if (!fd.isNil()) {
- args[0] = fd;
- return super.initialize(args, block);
- }
- }
- return openFile(args);
- }
- @JRubyMethod(name = "initialize", required = 1, optional = 2, visibility = PRIVATE, compat = RUBY1_9)
- public IRubyObject initialize19(ThreadContext context, IRubyObject[] args, Block block) {
- if (openFile == null) {
- throw context.getRuntime().newRuntimeError("reinitializing File");
- }
- if (args.length > 0 && args.length <= 3) {
- IRubyObject fd = TypeConverter.convertToTypeWithCheck(args[0], context.getRuntime().getFixnum(), "to_int");
- if (!fd.isNil()) {
- args[0] = fd;
- if (args.length == 1) {
- return super.initialize19(context, args[0], block);
- } else if (args.length == 2) {
- return super.initialize19(context, args[0], args[1], block);
- }
- return super.initialize19(context, args[0], args[1], args[2], block);
- }
- }
- return openFile19(context, args);
- }
- private IRubyObject openFile19(ThreadContext context, IRubyObject args[]) {
- Ruby runtime = context.getRuntime();
- RubyString filename = get_path(context, args[0]);
- runtime.checkSafeString(filename);
- path = adjustRootPathOnWindows(runtime, filename.getUnicodeValue(), runtime.getCurrentDirectory());
- String modeString = "r";
- ModeFlags modes = new ModeFlags();
- int perm = 0;
- try {
- if (args.length > 1) {
- modes = parseModes19(context, args[1]);
- if (args[1] instanceof RubyFixnum) {
- perm = getFilePermissions(args);
- } else {
- modeString = args[1].convertToString().toString();
- }
- } else {
- modes = parseModes19(context, RubyString.newString(runtime, modeString));
- }
- if (args.length > 2 && !args[2].isNil()) {
- if (args[2] instanceof RubyHash) {
- modes = parseOptions(context, args[2], modes);
- } else {
- perm = getFilePermissions(args);
- }
- }
- if (perm > 0) {
- sysopenInternal(path, modes, perm);
- } else {
- openInternal(path, modeString, modes);
- }
- } catch (InvalidValueException ex) {
- throw runtime.newErrnoEINVALError();
- }
- return this;
- }
-
- private IRubyObject openFile(IRubyObject args[]) {
- Ruby runtime = getRuntime();
- RubyString filename = get_path(runtime.getCurrentContext(), args[0]);
- runtime.checkSafeString(filename);
-
- path = adjustRootPathOnWindows(runtime, filename.getUnicodeValue(), runtime.getCurrentDirectory());
-
- String modeString;
- ModeFlags modes;
- int perm;
-
- try {
- if ((args.length > 1 && args[1] instanceof RubyFixnum) || (args.length > 2 && !args[2].isNil())) {
- modes = parseModes(args[1]);
- perm = getFilePermissions(args);
- sysopenInternal(path, modes, perm);
- } else {
- modeString = "r";
- if (args.length > 1 && !args[1].isNil()) {
- modeString = args[1].convertToString().toString();
- }
-
- openInternal(path, modeString);
- }
- } catch (InvalidValueException ex) {
- throw getRuntime().newErrnoEINVALError();
- } finally {}
-
- return this;
- }
- private int getFilePermissions(IRubyObject[] args) {
- return (args.length > 2 && !args[2].isNil()) ? RubyNumeric.num2int(args[2]) : 438;
- }
- protected void sysopenInternal(String path, ModeFlags modes, int perm) throws InvalidValueException {
- openFile = new OpenFile();
-
- openFile.setPath(path);
- openFile.setMode(modes.getOpenFileFlags());
- int umask = getUmaskSafe( getRuntime() );
- perm = perm - (perm & umask);
-
- ChannelDescriptor descriptor = sysopen(path, modes, perm);
- openFile.setMainStream(fdopen(descriptor, modes));
- }
- protected void openInternal(String path, String modeString, ModeFlags modes) throws InvalidValueException {
- openFile = new OpenFile();
- openFile.setMode(modes.getOpenFileFlags());
- openFile.setPath(path);
- openFile.setMainStream(fopen(path, modeString));
- }
-
- protected void openInternal(String path, String modeString) throws InvalidValueException {
- openFile = new OpenFile();
- openFile.setMode(getIOModes(getRuntime(), modeString).getOpenFileFlags());
- openFile.setPath(path);
- openFile.setMainStream(fopen(path, modeString));
- }
-
- private ChannelDescriptor sysopen(String path, ModeFlags modes, int perm) throws InvalidValueException {
- try {
- ChannelDescriptor descriptor = ChannelDescriptor.open(
- getRuntime().getCurrentDirectory(),
- path,
- modes,
- perm,
- getRuntime().getPosix(),
- getRuntime().getJRubyClassLoader());
- // TODO: check if too many open files, GC and try again
- return descriptor;
- } catch (PermissionDeniedException pde) {
- // PDException can be thrown only when creating the file and
- // permission is denied. See JavaDoc of PermissionDeniedException.
- throw getRuntime().newErrnoEACCESError(path);
- } catch (FileNotFoundException fnfe) {
- // FNFException can be thrown in both cases, when the file
- // is not found, or when permission is denied.
- if (Ruby.isSecurityRestricted() || new File(path).exists()) {
- throw getRuntime().newErrnoEACCESError(path);
- }
- throw getRuntime().newErrnoENOENTError(path);
- } catch (DirectoryAsFileException dafe) {
- throw getRuntime().newErrnoEISDirError();
- } catch (FileExistsException fee) {
- throw getRuntime().newErrnoEEXISTError(path);
- } catch (IOException ioe) {
- throw getRuntime().newIOErrorFromException(ioe);
- }
- }
-
- private Stream fopen(String path, String modeString) {
- try {
- Stream stream = ChannelStream.fopen(
- getRuntime(),
- path,
- getIOModes(getRuntime(), modeString));
-
- if (stream == null) {
- // TODO
- // if (errno == EMFILE || errno == ENFILE) {
- // rb_gc();
- // file = fopen(fname, mode);
- // }
- // if (!file) {
- // rb_sys_fail(fname);
- // }
- }
- // Do we need to be in SETVBUF mode for buffering to make sense? This comes up elsewhere.
- // #ifdef USE_SETVBUF
- // if (setvbuf(file, NULL, _IOFBF, 0) != 0)
- // rb_warn("setvbuf() can't be honoured for %s", fname);
- // #endif
- // #ifdef __human68k__
- // fmode(file, _IOTEXT);
- // #endif
- return stream;
- } catch (BadDescriptorException e) {
- throw getRuntime().newErrnoEBADFError();
- } catch (PermissionDeniedException pde) {
- // PDException can be thrown only when creating the file and
- // permission is denied. See JavaDoc of PermissionDeniedException.
- throw getRuntime().newErrnoEACCESError(path);
- } catch (FileNotFoundException ex) {
- // FNFException can be thrown in both cases, when the file
- // is not found, or when permission is denied.
- if (Ruby.isSecurityRestricted() || new File(path).exists()) {
- throw getRuntime().newErrnoEACCESError(path);
- }
- throw getRuntime().newErrnoENOENTError(path);
- } catch (DirectoryAsFileException ex) {
- throw getRuntime().newErrnoEISDirError();
- } catch (FileExistsException ex) {
- throw getRuntime().newErrnoEEXISTError(path);
- } catch (IOException ex) {
- throw getRuntime().newIOErrorFromException(ex);
- } catch (InvalidValueException ex) {
- throw getRuntime().newErrnoEINVALError();
- } catch (PipeException ex) {
- throw getRuntime().newErrnoEPIPEError();
- } catch (SecurityException ex) {
- throw getRuntime().newErrnoEACCESError(path);
- }
- }
- @JRubyMethod(required = 1)
- public IRubyObject chmod(ThreadContext context, IRubyObject arg) {
- checkClosed(context);
- int mode = (int) arg.convertToInteger().getLongValue();
- if (!new File(path).exists()) {
- throw context.getRuntime().newErrnoENOENTError(path);
- }
- return context.getRuntime().newFixnum(context.getRuntime().getPosix().chmod(path, mode));
- }
- @JRubyMethod(required = 2)
- public IRubyObject chown(ThreadContext context, IRubyObject arg1, IRubyObject arg2) {
- checkClosed(context);
- int owner = -1;
- if (!arg1.isNil()) {
- owner = RubyNumeric.num2int(arg1);
- }
- int group = -1;
- if (!arg2.isNil()) {
- group = RubyNumeric.num2int(arg2);
- }
- if (!new File(path).exists()) {
- throw context.getRuntime().newErrnoENOENTError(path);
- }
- return context.getRuntime().newFixnum(context.getRuntime().getPosix().chown(path, owner, group));
- }
- @JRubyMethod
- public IRubyObject atime(ThreadContext context) {
- checkClosed(context);
- return context.getRuntime().newFileStat(path, false).atime();
- }
- @JRubyMethod
- public IRubyObject ctime(ThreadContext context) {
- checkClosed(context);
- return context.getRuntime().newFileStat(path, false).ctime();
- }
- @JRubyMethod(required = 1)
- public IRubyObject lchmod(ThreadContext context, IRubyObject arg) {
- int mode = (int) arg.convertToInteger().getLongValue();
- if (!new File(path).exists()) {
- throw context.getRuntime().newErrnoENOENTError(path);
- }
- return context.getRuntime().newFixnum(context.getRuntime().getPosix().lchmod(path, mode));
- }
- // TODO: this method is not present in MRI!
- @JRubyMethod(required = 2)
- public IRubyObject lchown(ThreadContext context, IRubyObject arg1, IRubyObject arg2) {
- int owner = -1;
- if (!arg1.isNil()) {
- owner = RubyNumeric.num2int(arg1);
- }
- int group = -1;
- if (!arg2.isNil()) {
- group = RubyNumeric.num2int(arg2);
- }
- if (!new File(path).exists()) {
- throw context.getRuntime().newErrnoENOENTError(path);
- }
- return context.getRuntime().newFixnum(context.getRuntime().getPosix().lchown(path, owner, group));
- }
- @JRubyMethod
- public IRubyObject lstat(ThreadContext context) {
- checkClosed(context);
- return context.getRuntime().newFileStat(path, true);
- }
-
- @JRubyMethod
- public IRubyObject mtime(ThreadContext context) {
- checkClosed(context);
- return getLastModified(context.getRuntime(), path);
- }
- @JRubyMethod(meta = true, compat = RUBY1_9)
- public static IRubyObject path(ThreadContext context, IRubyObject self, IRubyObject str) {
- return get_path(context, str);
- }
- /**
- * similar in spirit to rb_get_path from 1.9 source
- * @param context
- * @param obj
- * @return
- */
- public static RubyString get_path(ThreadContext context, IRubyObject obj) {
- if (context.getRuntime().is1_9()) {
- if (obj instanceof RubyString) {
- return (RubyString)obj;
- }
-
- if (obj.respondsTo("to_path")) {
- obj = obj.callMethod(context, "to_path");
- }
- }
- return obj.convertToString();
- }
- /**
- * Get the fully-qualified JRubyFile object for the path, taking into
- * account the runtime's current directory.
- */
- public static JRubyFile file(IRubyObject pathOrFile) {
- Ruby runtime = pathOrFile.getRuntime();
- if (pathOrFile instanceof RubyFile) {
- return JRubyFile.create(runtime.getCurrentDirectory(), ((RubyFile) pathOrFile).getPath());
- } else {
- RubyString pathStr = get_path(runtime.getCurrentContext(), pathOrFile);
- String path = pathStr.getUnicodeValue();
- String[] pathParts = splitURI(path);
- if (pathParts != null && pathParts[0].startsWith("file:")) {
- path = pathParts[1];
- }
- return JRubyFile.create(runtime.getCurrentDirectory(), path);
- }
- }
- @JRubyMethod(name = {"path", "to_path"})
- public IRubyObject path(ThreadContext context) {
- IRubyObject newPath = context.getRuntime().getNil();
- if (path != null) {
- newPath = context.getRuntime().newString(path);
- newPath.setTaint(true);
- }
- return newPath;
- }
- @JRubyMethod
- @Override
- public IRubyObject stat(ThreadContext context) {
- checkClosed(context);
- return context.getRuntime().newFileStat(path, false);
- }
- @JRubyMethod(required = 1)
- public IRubyObject truncate(ThreadContext context, IRubyObject arg) {
- RubyInteger newLength = arg.convertToInteger();
- if (newLength.getLongValue() < 0) {
- throw context.getRuntime().newErrnoEINVALError(path);
- }
- try {
- openFile.checkWritable(context.getRuntime());
- openFile.getMainStream().ftruncate(newLength.getLongValue());
- } catch (BadDescriptorException e) {
- throw context.getRuntime().newErrnoEBADFError();
- } catch (PipeException e) {
- throw context.getRuntime().newErrnoESPIPEError();
- } catch (InvalidValueException ex) {
- throw context.getRuntime().newErrnoEINVALError();
- } catch (IOException e) {
- // Should we do anything?
- }
- return RubyFixnum.zero(context.getRuntime());
- }
- @Override
- public String toString() {
- return "RubyFile(" + path + ", " + openFile.getMode() + ", " + getRuntime().getFileno(openFile.getMainStream().getDescriptor()) + ")";
- }
- // TODO: This is also defined in the MetaClass too...Consolidate somewhere.
- private static ModeFlags getModes(Ruby runtime, IRubyObject object) throws InvalidValueException {
- if (object instanceof RubyString) {
- return getIOModes(runtime, ((RubyString) object).toString());
- } else if (object instanceof RubyFixnum) {
- return new ModeFlags(((RubyFixnum) object).getLongValue());
- }
- throw runtime.newTypeError("Invalid type for modes");
- }
- @JRubyMethod
- @Override
- public IRubyObject inspect() {
- StringBuilder val = new StringBuilder();
- val.append("#<File:").append(path);
- if(!openFile.isOpen()) {
- val.append(" (closed)");
- }
- val.append(">");
- return getRuntime().newString(val.toString());
- }
-
- /* File class methods */
-
- @JRubyMethod(required = 1, optional = 1, meta = true)
- public static IRubyObject basename(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- String name = get_path(context,args[0]).getUnicodeValue();
- // MRI-compatible basename handling for windows drive letter paths
- if (Platform.IS_WINDOWS) {
- if (name.length() > 1 && name.charAt(1) == ':' && Character.isLetter(name.charAt(0))) {
- switch (name.length()) {
- case 2:
- return RubyString.newEmptyString(context.getRuntime()).infectBy(args[0]);
- case 3:
- return context.getRuntime().newString(name.substring(2)).infectBy(args[0]);
- default:
- switch (name.charAt(2)) {
- case '/':
- case '\\':
- break;
- default:
- // strip c: away from relative-pathed name
- name = name.substring(2);
- break;
- }
- break;
- }
- }
- }
- while (name.length() > 1 && name.charAt(name.length() - 1) == '/') {
- name = name.substring(0, name.length() - 1);
- }
-
- // Paths which end in "/" or "\\" must be stripped off.
- int slashCount = 0;
- int length = name.length();
- for (int i = length - 1; i >= 0; i--) {
- char c = name.charAt(i);
- if (c != '/' && c != '\\') {
- break;
- }
- slashCount++;
- }
- if (slashCount > 0 && length > 1) {
- name = name.substring(0, name.length() - slashCount);
- }
-
- int index = name.lastIndexOf('/');
- if (index == -1) {
- // XXX actually only on windows...
- index = name.lastIndexOf('\\');
- }
-
- if (!name.equals("/") && index != -1) {
- name = name.substring(index + 1);
- }
-
- if (args.length == 2) {
- String ext = RubyString.stringValue(args[1]).toString();
- if (".*".equals(ext)) {
- index = name.lastIndexOf('.');
- if (index > 0) { // -1 no match; 0 it is dot file not extension
- name = name.substring(0, index);
- }
- } else if (name.endsWith(ext)) {
- name = name.substring(0, name.length() - ext.length());
- }
- }
- return context.getRuntime().newString(name).infectBy(args[0]);
- }
- @JRubyMethod(required = 2, rest = true, meta = true)
- public static IRubyObject chmod(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- Ruby runtime = context.getRuntime();
-
- int count = 0;
- RubyInteger mode = args[0].convertToInteger();
- for (int i = 1; i < args.length; i++) {
- JRubyFile filename = file(args[i]);
-
- if (!filename.exists()) {
- throw runtime.newErrnoENOENTError(filename.toString());
- }
-
- boolean result = 0 == runtime.getPosix().chmod(filename.getAbsolutePath(), (int)mode.getLongValue());
- if (result) {
- count++;
- }
- }
-
- return runtime.newFixnum(count);
- }
-
- @JRubyMethod(required = 3, rest = true, meta = true)
- public static IRubyObject chown(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- Ruby runtime = context.getRuntime();
- int count = 0;
- int owner = -1;
- if (!args[0].isNil()) {
- owner = RubyNumeric.num2int(args[0]);
- }
- int group = -1;
- if (!args[1].isNil()) {
- group = RubyNumeric.num2int(args[1]);
- }
- for (int i = 2; i < args.length; i++) {
- JRubyFile filename = file(args[i]);
- if (!filename.exists()) {
- throw runtime.newErrnoENOENTError(filename.toString());
- }
-
- boolean result = 0 == runtime.getPosix().chown(filename.getAbsolutePath(), owner, group);
- if (result) {
- count++;
- }
- }
-
- return runtime.newFixnum(count);
- }
-
- @JRubyMethod(required = 1, meta = true)
- public static IRubyObject dirname(ThreadContext context, IRubyObject recv, IRubyObject arg) {
- RubyString filename = get_path(context, arg);
-
- String jfilename = filename.getUnicodeValue();
-
- String name = jfilename.replace('\\', '/');
- int minPathLength = 1;
- boolean trimmedSlashes = false;
- boolean startsWithDriveLetterOnWindows = startsWithDriveLetterOnWindows(name);
- if (startsWithDriveLetterOnWindows) {
- minPathLength = 3;
- }
- while (name.length() > minPathLength && name.charAt(name.length() - 1) == '/') {
- trimmedSlashes = true;
- name = name.substring(0, name.length() - 1);
- }
- String result;
- if (startsWithDriveLetterOnWindows && name.length() == 2) {
- if (trimmedSlashes) {
- // C:\ is returned unchanged
- result = jfilename.substring(0, 3);
- } else {
- result = jfilename.substring(0, 2) + '.';
- }
- } else {
- //TODO deal with UNC names
- int index = name.lastIndexOf('/');
- if (index == -1) {
- if (startsWithDriveLetterOnWindows) {
- return context.getRuntime().newString(jfilename.substring(0, 2) + ".");
- } else {
- return context.getRuntime().newString(".");
- }
- }
- if (index == 0) return context.getRuntime().newString("/");
- if (startsWithDriveLetterOnWindows && index == 2) {
- // Include additional path separator
- // (so that dirname of "C:\file.txt" is "C:\", not "C:")
- index++;
- }
- result = jfilename.substring(0, index);
- }
- char endChar;
- // trim trailing slashes
- while (result.length() > minPathLength) {
- endChar = result.charAt(result.length() - 1);
- if (endChar == '/' || endChar == '\\') {
- result = result.substring(0, result.length() - 1);
- } else {
- break;
- }
- }
- return context.getRuntime().newString(result).infectBy(filename);
- }
- private static boolean isWindowsDriveLetter(char c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
- }
- /**
- * Returns the extension name of the file. An empty string is returned if
- * the filename (not the entire path) starts or ends with a dot.
- * @param recv
- * @param arg Path to get extension name of
- * @return Extension, including the dot, or an empty string
- */
- @JRubyMethod(required = 1, meta = true)
- public static IRubyObject extname(ThreadContext context, IRubyObject recv, IRubyObject arg) {
- IRubyObject baseFilename = basename(context, recv, new IRubyObject[]{arg});
-
- String filename = RubyString.stringValue(baseFilename).getUnicodeValue();
- String result = "";
- int dotIndex = filename.lastIndexOf(".");
- if (dotIndex > 0 && dotIndex != (filename.length() - 1)) {
- // Dot is not at beginning and not at end of filename.
- result = filename.substring(dotIndex);
- }
- return context.getRuntime().newString(result);
- }
- /**
- * Converts a pathname to an absolute pathname. Relative paths are
- * referenced from the current working directory of the process unless
- * a second argument is given, in which case it will be used as the
- * starting point. If the second argument is also relative, it will
- * first be converted to an absolute pathname.
- * @param recv
- * @param args
- * @return Resulting absolute path as a String
- */
- @JRubyMethod(required = 1, optional = 1, meta = true, compat = CompatVersion.RUBY1_8)
- public static IRubyObject expand_path(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- return expandPathInternal(context, recv, args, true);
- }
- @JRubyMethod(name = "expand_path", required = 1, optional = 1, meta = true, compat = CompatVersion.RUBY1_9)
- public static IRubyObject expand_path19(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- RubyString path = (RubyString) expandPathInternal(context, recv, args, true);
- path.force_encoding(context, context.getRuntime().getEncodingService().getDefaultExternal());
- return path;
- }
- /**
- * ---------------------------------------------------- File::absolute_path
- * File.absolute_path(file_name [, dir_string] ) -> abs_file_name
- *
- * From Ruby 1.9.1
- * ------------------------------------------------------------------------
- * Converts a pathname to an absolute pathname. Relative paths are
- * referenced from the current working directory of the process unless
- * _dir_string_ is given, in which case it will be used as the
- * starting point. If the given pathname starts with a ``+~+'' it is
- * NOT expanded, it is treated as a normal directory name.
- *
- * File.absolute_path("~oracle/bin") #=> "<relative_path>/~oracle/bin"
- *
- * @param context
- * @param recv
- * @param args
- * @return
- */
- @JRubyMethod(required = 1, optional = 1, meta = true, compat = RUBY1_9)
- public static IRubyObject absolute_path(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- return expandPathInternal(context, recv, args, false);
- }
- @JRubyMethod(name = {"realdirpath"}, required = 1, optional = 1, meta = true, compat = RUBY1_9)
- public static IRubyObject realdirpath(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- return expandPathInternal(context, recv, args, false);
- }
- @JRubyMethod(name = {"realpath"}, required = 1, optional = 1, meta = true, compat = RUBY1_9)
- public static IRubyObject realpath(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- IRubyObject file = expandPathInternal(context, recv, args, false);
- if (!RubyFileTest.exist_p(recv, file).isTrue()) {
- throw context.getRuntime().newErrnoENOENTError(file.toString());
- }
- return file;
- }
- private static IRubyObject expandPathInternal(ThreadContext context, IRubyObject recv, IRubyObject[] args, boolean expandUser) {
- Ruby runtime = context.getRuntime();
- String relativePath = get_path(context, args[0]).getUnicodeValue();
- String[] uriParts = splitURI(relativePath);
- String cwd = null;
- // Handle ~user paths
- if (expandUser) {
- relativePath = expandUserPath(context, relativePath);
- }
- if (uriParts != null) {
- relativePath = uriParts[1];
- }
- // If there's a second argument, it's the path to which the first
- // argument is relative.
- if (args.length == 2 && !args[1].isNil()) {
- cwd = get_path(context, args[1]).getUnicodeValue();
- // Handle ~user paths.
- if (expandUser) {
- cwd = expandUserPath(context, cwd);
- }
- String[] cwdURIParts = splitURI(cwd);
- if (uriParts == null && cwdURIParts != null) {
- uriParts = cwdURIParts;
- cwd = cwdURIParts[1];
- }
- cwd = adjustRootPathOnWindows(runtime, cwd, null);
- boolean startsWithSlashNotOnWindows = (cwd != null)
- && !Platform.IS_WINDOWS && cwd.length() > 0
- && cwd.charAt(0) == '/';
- // TODO: better detection when path is absolute or not.
- // If the path isn't absolute, then prepend the current working
- // directory to the path.
- if (!startsWithSlashNotOnWindows && !startsWithDriveLetterOnWindows(cwd)) {
- cwd = new File(runtime.getCurrentDirectory(), cwd).getAbsolutePath();
- }
- } else {
- // If there's no second argument, simply use the working directory
- // of the runtime.
- cwd = runtime.getCurrentDirectory();
- }
-
- // Something wrong we don't know the cwd...
- // TODO: Is this behavior really desirable? /mov
- if (cwd == null) return runtime.getNil();
-
- /* The counting of slashes that follows is simply a way to adhere to
- * Ruby's UNC (or something) compatibility. When Ruby's expand_path is
- * called with "//foo//bar" it will return "//foo/bar". JRuby uses
- * java.io.File, and hence returns "/foo/bar". In order to retain
- * java.io.File in the lower layers and provide full Ruby
- * compatibility, the number of extra slashes must be counted and
- * prepended to the result.
- */
- // TODO: special handling on windows for some corner cases
- // if (IS_WINDOWS) {
- // if (relativePath.startsWith("//")) {
- // if (relativePath.length() > 2 && relativePath.charAt(2) != '/') {
- // int nextSlash = relativePath.indexOf('/', 3);
- // if (nextSlash != -1) {
- // return runtime.newString(
- // relativePath.substring(0, nextSlash)
- // + canonicalize(relativePath.substring(nextSlash)));
- // } else {
- // return runtime.newString(relativePath);
- // }
- // }
- // }
- // }
- // Find out which string to check.
- String padSlashes = "";
- if (uriParts != null) {
- padSlashes = uriParts[0];
- } else if (!Platform.IS_WINDOWS) {
- if (relativePath.length() > 0 && relativePath.charAt(0) == '/') {
- padSlashes = countSlashes(relativePath);
- } else if (cwd.length() > 0 && cwd.charAt(0) == '/') {
- padSlashes = countSlashes(cwd);
- }
- }
-
- JRubyFile path;
-
- if (relativePath.length() == 0) {
- path = JRubyFile.create(relativePath, cwd);
- } else {
- relativePath = adjustRootPathOnWindows(runtime, relativePath, cwd);
- path = JRubyFile.create(cwd, relativePath);
- }
- return runtime.newString(padSlashes + canonicalize(path.getAbsolutePath()));
- }
- private static Pattern URI_PREFIX = Pattern.compile("^[a-z]{2,}:");
- public static String[] splitURI(String path) {
- Matcher m = URI_PREFIX.matcher(path);
- if (m.find()) {
- try {
- URI u = new URI(path);
- String pathPart = u.getPath();
- return new String[] {path.substring(0, path.indexOf(pathPart)), pathPart};
- } catch (Exception e) {
- try {
- URL u = new URL(path);
- String pathPart = u.getPath();
- return new String[] {path.substring(0, path.indexOf(pathPart)), pathPart};
- } catch (Exception e2) {
- }
- }
- }
- return null;
- }
- /**
- * This method checks a path, and if it starts with ~, then it expands
- * the path to the absolute path of the user's home directory. If the
- * string does not begin with ~, then the string is simply returned.
- * unaltered.
- * @param recv
- * @param path Path to check
- * @return Expanded path
- */
- public static String expandUserPath(ThreadContext context, String path) {
-
- int pathLength = path.length();
- if (pathLength >= 1 && path.charAt(0) == '~') {
- // Enebo : Should ~frogger\\foo work (it doesnt in linux ruby)?
- int userEnd = path.indexOf('/');
-
- if (userEnd == -1) {
- if (pathLength == 1) {
- // Single '~' as whole path to expand
- path = RubyDir.getHomeDirectoryPath(context).toString();
- } else {
- // No directory delimeter. Rest of string is username
- userEnd = pathLength;
- }
- }
-
- if (userEnd == 1) {
- // '~/...' as path to expand
- path = RubyDir.getHomeDirectoryPath(context).toString() +
- path.substring(1);
- } else if (userEnd > 1){
- // '~user/...' as path to expand
- String user = path.substring(1, userEnd);
- IRubyObject dir = RubyDir.getHomeDirectoryPath(context, user);
-
- if (dir.isNil()) {
- throw context.getRuntime().newArgumentError("user " + user + " does not exist");
- }
-
- path = "" + dir + (pathLength == userEnd ? "" : path.substring(userEnd));
- }
- }
- return path;
- }
- private static final String[] SLASHES = {"", "/", "//"};
- /**
- * Returns a string consisting of <code>n-1</code> slashes, where
- * <code>n</code> is the number of slashes at the beginning of the input
- * string.
- * @param stringToCheck
- * @return
- */
- private static String countSlashes( String stringToCheck ) {
- // Count number of extra slashes in the beginning of the string.
- int slashCount = 0;
- for (int i = 0; i < stringToCheck.length(); i++) {
- if (stringToCheck.charAt(i) == '/') {
- slashCount++;
- } else {
- break;
- }
- }
- // If there are N slashes, then we want N-1.
- if (slashCount > 0) {
- slashCount--;
- }
- if (slashCount < SLASHES.length) {
- return SLASHES[slashCount];
- }
-
- // Prepare a string with the same number of redundant slashes so that
- // we easily can prepend it to the result.
- char[] slashes = new char[slashCount];
- for (int i = 0; i < slashCount; i++) {
- slashes[i] = '/';
- }
- return new String(slashes);
- }
- public static String canonicalize(String path) {
- return canonicalize(null, path);
- }
- private static String canonicalize(String canonicalPath, String remaining) {
- if (remaining == null) {
- if ("".equals(canonicalPath)) {
- return "/";
- } else {
- // compensate for missing slash after drive letter on windows
- if (startsWithDriveLetterOnWindows(canonicalPath)
- && canonicalPath.length() == 2) {
- canonicalPath += "/";
- }
- }
- return canonicalPath;
- }
- String child;
- int slash = remaining.indexOf('/');
- if (slash == -1) {
- child = remaining;
- remaining = null;
- } else {
- child = remaining.substring(0, slash);
- remaining = remaining.substring(slash + 1);
- }
- if (child.equals(".")) {
- // no canonical path yet or length is zero, and we have a / followed by a dot...
- if (slash == -1) {
- // we don't have another slash after this, so replace /. with /
- if (canonicalPath != null && canonicalPath.length() == 0 && slash == -1) canonicalPath += "/";
- } else {
- // we do have another slash; omit both / and . (JRUBY-1606)
- }
- } else if (child.equals("..")) {
- if (canonicalPath == null) throw new IllegalArgumentException("Cannot have .. at the start of an absolute path");
- int lastDir = canonicalPath.lastIndexOf('/');
- if (lastDir == -1) {
- if (startsWithDriveLetterOnWindows(canonicalPath)) {
- // do nothing, we should not delete the drive letter
- } else {
- canonicalPath = "";
- }
- } else {
- canonicalPath = canonicalPath.substring(0, lastDir);
- }
- } else if (canonicalPath == null) {
- canonicalPath = child;
- } else {
- canonicalPath += "/" + child;
- }
- return canonicalize(canonicalPath, remaining);
- }
- /**
- * Returns true if path matches against pattern The pattern is not a regular expression;
- * instead it follows rules similar to shell filename globbing. It may contain the following
- * metacharacters:
- * *: Glob - match any sequence chars (re: .*). If like begins with '.' then it doesn't.
- * ?: Matches a single char (re: .).
- * [set]: Matches a single char in a set (re: [...]).
- *
- */
- @JRubyMethod(name = {"fnmatch", "fnmatch?"}, required = 2, optional = 1, meta = true)
- public static IRubyObject fnmatch(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- int flags = args.length == 3 ? RubyNumeric.num2int(args[2]) : 0;
- ByteList pattern = args[0].convertToString().getByteList();
- ByteList path = get_path(context, args[1]).getByteList();
- if (org.jruby.util.Dir.fnmatch(pattern.getUnsafeBytes(), pattern.getBegin(), pattern.getBegin()+pattern.getRealSize(), path.getUnsafeBytes(), path.getBegin(), path.getBegin()+path.getRealSize(), flags) == 0) {
- return context.getRuntime().getTrue();
- }
- return context.getRuntime().getFalse();
- }
-
- @JRubyMethod(name = "ftype", required = 1, meta = true)
- public static IRubyObject ftype(ThreadContext context, IRubyObject recv, IRubyObject filename) {
- return context.getRuntime().newFileStat(get_path(context, filename).getUnicodeValue(), true).ftype();
- }
- private static String inspectJoin(ThreadContext context, IRubyObject recv, RubyArray parent, RubyArray array) {
- Ruby runtime = context.getRuntime();
-
- // If already inspecting, there is no need to register/unregister again.
- if (runtime.isInspecting(parent)) return join(context, recv, array).toString();
- try {
- runtime.registerInspecting(parent);
- return join(context, recv, array).toString();
- } finally {
- runtime.unregisterInspecting(parent);
- }
- }
- private static RubyString join(ThreadContext context, IRubyObject recv, RubyArray ary) {
- IRubyObject[] args = ary.toJavaArray();
- boolean isTainted = false;
- StringBuilder buffer = new StringBuilder();
- Ruby runtime = context.getRuntime();
-
- for (int i = 0; i < args.length; i++) {
- if (args[i].isTaint()) {
- isTainted = true;
- }
- String element;
- if (args[i] instanceof RubyString) {
- element = args[i].convertToString().getUnicodeValue();
- } else if (args[i] instanceof RubyArray) {
- if (runtime.isInspecting(args[i])) {
- throw runtime.newArgumentError("recursive array");
- } else {
- element = inspectJoin(context, recv, ary, ((RubyArray)args[i]));
- }
- } else {
- RubyString path = get_path(context, args[i]);
- element = path.getUnicodeValue();
- }
-
- chomp(buffer);
- if (i > 0 && !element.startsWith("/") && !element.startsWith("\\")) {
- buffer.append("/");
- }
- buffer.append(element);
- }
-
- RubyString fixedStr = RubyString.newString(runtime, buffer.toString());
- fixedStr.setTaint(isTainted);
- return fixedStr;
- }
-
- /*
- * Fixme: This does not have exact same semantics as RubyArray.join, but they
- * probably could be consolidated (perhaps as join(args[], sep, doChomp)).
- */
- @JRubyMethod(rest = true, meta = true)
- public static RubyString join(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- return join(context, recv, RubyArray.newArrayNoCopyLight(context.getRuntime(), args));
- }
- private static void chomp(StringBuilder buffer) {
- int lastIndex = buffer.length() - 1;
-
- while (lastIndex >= 0 && (buffer.lastIndexOf("/") == lastIndex || buffer.lastIndexOf("\\") == lastIndex)) {
- buffer.setLength(lastIndex);
- lastIndex--;
- }
- }
-
- @JRubyMethod(name = "lstat", required = 1, meta = true)
- public static IRubyObject lstat(ThreadContext context, IRubyObject recv, IRubyObject filename) {
- String f = get_path(context, filename).getUnicodeValue();
- return context.getRuntime().newFileStat(f, true);
- }
- @JRubyMethod(name = "stat", required = 1, meta = true)
- public static IRubyObject stat(ThreadContext context, IRubyObject recv, IRubyObject filename) {
- String f = get_path(context, filename).getUnicodeValue();
- return context.getRuntime().newFileStat(f, false);
- }
- @JRubyMethod(name = "atime", required = 1, meta = true)
- public static IRubyObject atime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
- String f = get_path(context, filename).getUnicodeValue();
- return context.getRuntime().newFileStat(f, false).atime();
- }
- @JRubyMethod(name = "ctime", required = 1, meta = true)
- public static IRubyObject ctime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
- String f = get_path(context, filename).getUnicodeValue();
- return context.getRuntime().newFileStat(f, false).ctime();
- }
- @JRubyMethod(required = 2, rest = true, meta = true)
- public static IRubyObject lchmod(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- Ruby runtime = context.getRuntime();
-
- int count = 0;
- RubyInteger mode = args[0].convertToInteger();
- for (int i = 1; i < args.length; i++) {
- RubyString filename = get_path(context, args[i]);
-
- if (!RubyFileTest.exist_p(filename, filename).isTrue()) {
- throw runtime.newErrnoENOENTError(filename.toString());
- }
-
- boolean result = 0 == runtime.getPosix().lchmod(filename.getUnicodeValue(), (int)mode.getLongValue());
- if (result) {
- count++;
- }
- }
-
- return runtime.newFixnum(count);
- }
- @JRubyMethod(required = 2, rest = true, meta = true)
- public static IRubyObject lchown(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- Ruby runtime = context.getRuntime();
- int owner = !args[0].isNil() ? RubyNumeric.num2int(args[0]) : -1;
- int group = !args[1].isNil() ? RubyNumeric.num2int(args[1]) : -1;
- int count = 0;
- for (int i = 2; i < args.length; i++) {
- IRubyObject filename = args[i];
- if (0 != runtime.getPosix().lchown(filename.toString(), owner, group)) {
- throw runtime.newErrnoFromLastPOSIXErrno();
- } else {
- count++;
- }
- }
- return runtime.newFixnum(count);
- }
- @JRubyMethod(required = 2, meta = true, backtrace = true)
- public static IRubyObject link(ThreadContext context, IRubyObject recv, IRubyObject from, IRubyObject to) {
- Ruby runtime = context.getRuntime();
- RubyString fromStr = RubyString.stringValue(from);
- RubyString toStr = RubyString.stringValue(to);
- int ret = runtime.getPosix().link(fromStr.getUnicodeValue(), toStr.getUnicodeValue());
- if (ret != 0) {
- // In most cases, when there is an error during the call,
- // the POSIX handler throws an exception, but not in case
- // with pure Java POSIX layer (when native support is disabled),
- // so we deal with it like this:
- throw runtime.newErrnoEEXISTError(fromStr + " or " + toStr);
- }
- return runtime.newFixnum(ret);
- }
- @JRubyMethod(name = "mtime", required = 1, meta = true)
- public static IRubyObject mtime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
- return getLastModified(context.getRuntime(), get_path(context, filename).getUnicodeValue());
- }
-
- @JRubyMethod(required = 2, meta = true)
- public static IRubyObject rename(ThreadContext context, IRubyObject recv, IRubyObject oldName, IRubyObject newName) {
- Ruby runtime = context.getRuntime();
- RubyString oldNameString = RubyString.stringValue(oldName);
- RubyString newNameString = RubyString.stringValue(newName);
- runtime.checkSafeString(oldNameString);
- runtime.checkSafeString(newNameString);
- String newNameJavaString = newNameString.getUnicodeValue();
- String oldNameJavaString = oldNameString.getUnicodeValue();
- JRubyFile oldFile = JRubyFile.create(runtime.getCurrentDirectory(), oldNameJavaString);
- JRubyFile newFile = JRubyFile.create(runtime.getCurrentDirectory(), newNameJavaString);
-
- if (!oldFile.exists() || !newFile.getParentFile().exists()) {
- throw runtime.newErrnoENOENTError(oldNameJavaString + " or " + newNameJavaString);
- }
- JRubyFile dest = JRubyFile.create(runtime.getCurrentDirectory(), newNameJavaString);
- if (oldFile.renameTo(dest)) { // rename is successful
- return RubyFixnum.zero(runtime);
- }
- // rename via Java API call wasn't successful, let's try some tricks, similar to MRI
- if (newFile.exists()) {
- runtime.getPosix().chmod(newNameJavaString, 0666);
- newFile.delete();
- }
- if (oldFile.renameTo(dest)) { // try to rename one more time
- return RubyFixnum.zero(runtime);
- }
- throw runtime.newErrnoEACCESError(oldNameJavaString + " or " + newNameJavaString);
- }
-
- @JRubyMethod(required = 1, meta = true)
- public static RubyArray split(ThreadContext context, IRubyObject recv, IRubyObject arg) {
- RubyString filename = get_path(context, arg);
-
- return context.getRuntime().newArray(dirname(context, recv, filename),
- basename(context, recv, new IRubyObject[] { filename }));
- }
-
- @JRubyMethod(required = 2, meta = true)
- public static IRubyObject symlink(ThreadContext context, IRubyObject recv, IRubyObject from, IRubyObject to) {
- Ruby runtime = context.getRuntime();
- RubyString fromStr = get_path(context, from);
- RubyString toStr = get_path(context, to);
- String tovalue = toStr.getUnicodeValue();
- tovalue = JRubyFile.create(runtime.getCurrentDirectory(), tovalue).getAbsolutePath();
- try {
- if (runtime.getPosix().symlink(
- fromStr.getUnicodeValue(), tovalue) == -1) {
- // FIXME: When we get JNA3 we need to properly write this to errno.
- throw runtime.newErrnoEEXISTError(fromStr + " or " + toStr);
- }
- } catch (java.lang.UnsatisfiedLinkError ule) {
- throw runtime.newNotImplementedError("symlink() function is unimplemented on this machine");
- }
-
- return runtime.newFixnum(0);
- }
-
- @JRubyMethod(required = 1, meta = true)
- public static IRubyObject readlink(ThreadContext context, IRubyObject recv, IRubyObject path) {
- Ruby runtime = context.getRuntime();
-
- try {
- String realPath = runtime.getPosix().readlink(path.convertToString().getUnicodeValue());
-
- if (!RubyFileTest.exist_p(recv, path).isTrue()) {
- throw runtime.newErrnoENOENTError(path.toString());
- }
-
- if (!RubyFileTest.symlink_p(recv, path).isTrue()) {
- throw runtime.newErrnoEINVALError(path.toString());
- }
-
- if (realPath == null) {
- //FIXME: When we get JNA3 we need to properly write this to errno.
- }
- return runtime.newString(realPath);
- } catch (IOException e) {
- throw runtime.newIOError(e.getMessage());
- }
- }
- // Can we produce IOError which bypasses a close?
- @JRubyMethod(required = 2, meta = true, compat = RUBY1_8)
- public static IRubyObject truncate(ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
- return truncateCommon(context, recv, arg1, arg2);
- }
- @JRubyMethod(name = "truncate", required = 2, meta = true, compat = RUBY1_9)
- public static IRubyObject truncate19(ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
- if (!(arg1 instanceof RubyString) && arg1.respondsTo("to_path")) {
- arg1 = arg1.callMethod(context, "to_path");
- }
- return truncateCommon(context, recv, arg1, arg2);
- }
- private static IRubyObject truncateCommon(ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
- RubyString filename = arg1.convertToString(); // TODO: SafeStringValue here
- Ruby runtime = context.getRuntime();
- RubyInteger newLength = arg2.convertToInteger();
- File testFile ;
- File childFile = new File(filename.getUnicodeValue() );
- if ( childFile.isAbsolute() ) {
- testFile = childFile ;
- } else {
- testFile = new File(runtime.getCurrentDirectory(), filename.getByteList().toString());
- }
- if (!testFile.exists()) {
- throw runtime.newErrnoENOENTError(filename.getByteList().toString());
- }
- if (newLength.getLongValue() < 0) {
- throw runtime.newErrnoEINVALError(filename.toString());
- }
- IRubyObject[] args = new IRubyObject[] { filename, runtime.newString("r+") };
- RubyFile file = (RubyFile) open(context, recv, args, Block.NULL_BLOCK);
- file.truncate(context, newLength);
- file.close();
- return RubyFixnum.zero(runtime);
- }
- @JRubyMethod(meta = true, optional = 1)
- public static IRubyObject umask(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- Ruby runtime = context.getRuntime();
- int oldMask = 0;
- if (args.length == 0) {
- oldMask = getUmaskSafe( runtime );
- } else if (args.length == 1) {
- int newMask = (int) args[0].convertToInteger().getLongValue();
- synchronized (_umaskLock) {
- oldMask = runtime.getPosix().umask(newMask);
- _cachedUmask = newMask;
- }
- } else {
- runtime.newArgumentError("wrong number of arguments");
- }
-
- return runtime.newFixnum(oldMask);
- }
- @JRubyMethod(required = 2, rest = true, meta = true)
- public static IRubyObject utime(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- Ruby runtime = context.getRuntime();
- long[] atimeval = null;
- long[] mtimeval = null;
- if (args[0] != runtime.getNil() || args[1] != runtime.getNil()) {
- atimeval = extractTimeval(runtime, args[0]);
- mtimeval = extractTimeval(runtime, args[1]);
- }
- for (int i = 2, j = args.length; i < j; i++) {
- RubyString filename = get_path(context, args[i]);
- runtime.checkSafeString(filename);
-
- JRubyFile fileToTouch = JRubyFile.create(runtime.getCurrentDirectory(),filename.getUnicodeValue());
-
- if (!fileToTouch.exists()) {
- throw runtime.newErrnoENOENTError(filename.toString());
- }
- runtime.getPosix().utimes(fileToTouch.getAbsolutePath(), atimeval, mtimeval);
- }
-
- return runtime.newFixnum(args.length - 2);
- }
-
- @JRubyMethod(name = {"unlink", "delete"}, rest = true, meta = true)
- public static IRubyObject unlink(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
- Ruby runtime = context.getRuntime();
-
- for (int i = 0; i < args.length; i++) {
- RubyString filename = get_path(context, args[i]);
- runtime.checkSafeString(filename);
- JRubyFile lToDelete = JRubyFile.create(runtime.getCurrentDirectory(), filename.getUnicodeValue());
-
- boolean isSymlink = RubyFileTest.symlink_p(recv, filename).isTrue();
- // Broken symlinks considered by exists() as non-existing,
- // so we need to check for symlinks explicitly.
- if (!lToDelete.exists() && !isSymlink) {
- throw runtime.newErrnoENOENTError(filename.getUnicodeValue());
- }
- if (lToDelete.isDirectory() && !isSymlink) {
- throw runtime.newErrnoEPERMError(filename.getUnicodeValue());
- }
- if (!lToDelete.delete()) {
- throw runtime.newErrnoEACCESError(filename.getUnicodeValue());
- }
- }
-
- return runtime.newFixnum(args.length);
- }
- @JRubyMethod(name = "size", backtrace = true, compat = RUBY1_9)
- public IRubyObject size(ThreadContext context) {
- Ruby runtime = context.getRuntime();
- if ((openFile.getMode() & OpenFile.WRITABLE) != 0) {
- flush();
- }
- FileStat stat = runtime.getPosix().fstat(
- getOpenFileChecked().getMainStream().getDescriptor().getFileDescriptor());
- if (stat == null) {
- throw runtime.newErrnoEACCESError(path);
- }
- return runtime.newFixnum(stat.st_size());
- }
- public static ZipEntry getFileEntry(ZipFile zf, String path) throws IOException {
- ZipEntry entry = zf.getEntry(path);
- if (entry == null) {
- // try canonicalizing the path to eliminate . and .. (JRUBY-4760, JRUBY-4879)
- String prefix = new File(".").getCanonicalPath();
- entry = zf.getEntry(new File(path).getCanonicalPath().substring(prefix.length() + 1).replaceAll("\\\\", "/"));
- }
- return entry;
- }
- public static ZipEntry getDirOrFileEntry(ZipFile zf, String path) throws IOException {
- ZipEntry entry = zf.getEntry(path + "/"); // first try as directory
- if (entry == null) {
- // try canonicalizing the path to eliminate . and .. (JRUBY-4760, JRUBY-4879)
- String prefix = new File(".").getCanonicalPath();
- entry = zf.getEntry(new File(path + "/").getCanonicalPath().substring(prefix.length() + 1).replaceAll("\\\\", "/"));
- if (entry == null) {
- // try as file
- entry = getFileEntry(zf, path);
- }
- }
- return entry;
- }
- /**
- * Joy of POSIX, only way to get the umask is to set the umask,
- * then set it back. That's unsafe in a threaded program. We
- * minimize but may not totally remove this race by caching the
- * obtained or previously set (see umask() above) umask and using
- * that as the initial set value which, cross fingers, is a
- * no-op. The cache access is then synchronized. TODO: Better?
- */
- private static int getUmaskSafe( Ruby runtime ) {
- synchronized (_umaskLock) {
- final int umask = runtime.getPosix().umask(_cachedUmask);
- if (_cachedUmask != umask ) {
- runtime.getPosix().umask(umask);
- _cachedUmask = umask;
- }
- return umask;
- }
- }
- /**
- * Extract a timeval (an array of 2 longs: seconds and microseconds from epoch) from
- * an IRubyObject.
- */
- private static long[] extractTimeval(Ruby runtime, IRubyObject value) {
- long[] timeval = new long[2];
- if (value instanceof RubyFloat) {
- timeval[0] = Platform.IS_32_BIT ? RubyNumeric.num2int(value) : RubyNumeric.num2long(value);
- double fraction = ((RubyFloat) value).getDoubleValue() % 1.0;
- timeval[1] = (long)(fraction * 1e6 + 0.5);
- } else if (value instanceof RubyNumeric) {
- timeval[0] = Platform.IS_32_BIT ? RubyNumeric.num2int(value) : RubyNumeric.num2long(value);
- timeval[1] = 0;
- } else {
- RubyTime time;
- if (value instanceof RubyTime) {
- time = ((RubyTime) value);
- } else {
- time = (RubyTime) TypeConverter.convertToType(value, runtime.getTime(), "to_time", true);
- }
- timeval[0] = Platform.IS_32_BIT ? RubyNumeric.num2int(time.to_i()) : RubyNumeric.num2long(time.to_i());
- timeval[1] = Platform.IS_32_BIT ? RubyNumeric.num2int(time.usec()) : RubyNumeric.num2long(time.usec());
- }
- return timeval;
- }
- // Fast path since JNA stat is about 10x slower than this
- private static IRubyObject getLastModified(Ruby runtime, String path) {
- JRubyFile file = JRubyFile.create(runtime.getCurrentDirectory(), path);
-
- if (!file.exists()) {
- throw runtime.newErrnoENOENTError(path);
- }
-
- return runtime.newTime(file.lastModified());
- }
- private void checkClosed(ThreadContext context) {
- openFile.checkClosed(context.getRuntime());
- }
- }