package org.jruby;
import jnr.constants.platform.OpenFlags;
import jnr.posix.POSIX;
import jnr.posix.util.Platform;
import org.jcodings.Encoding;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.NotImplementedError;
import org.jruby.runtime.*;
import org.jruby.runtime.JavaSites.FileSites;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.encoding.EncodingCapable;
import org.jruby.runtime.encoding.EncodingService;
import org.jruby.util.*;
import org.jruby.util.io.EncodingUtils;
import org.jruby.util.io.IOEncodable;
import org.jruby.util.io.OpenFile;
import org.jruby.util.io.PosixShim;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributeView;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static org.jruby.RubyInteger.singleCharByteList;
import static org.jruby.runtime.Visibility.PRIVATE;
import static org.jruby.util.StringSupport.*;
import static org.jruby.util.io.EncodingUtils.vmode;
import static org.jruby.util.io.EncodingUtils.vperm;
@JRubyClass(name="File", parent="IO", include="FileTest")
public class RubyFile extends RubyIO implements EncodingCapable {
static final ByteList SLASH = singleCharByteList((byte) '/');
static final ByteList BACKSLASH = singleCharByteList((byte) '\\');
public static RubyClass createFileClass(Ruby runtime) {
ThreadContext context = runtime.getCurrentContext();
RubyClass fileClass = runtime.defineClass("File", runtime.getIO(), FILE_ALLOCATOR);
runtime.setFile(fileClass);
fileClass.defineAnnotatedMethods(RubyFile.class);
fileClass.setClassIndex(ClassIndex.FILE);
fileClass.setReifiedClass(RubyFile.class);
fileClass.kindOf = new RubyModule.JavaClassKindOf(RubyFile.class);
RubyString separator = RubyString.newString(runtime, SLASH);
separator.freeze(context);
fileClass.defineConstant("SEPARATOR", separator);
fileClass.defineConstant("Separator", separator);
if (File.separatorChar == '\\') {
RubyString altSeparator = RubyString.newString(runtime, BACKSLASH);
altSeparator.freeze(context);
fileClass.defineConstant("ALT_SEPARATOR", altSeparator);
} else {
fileClass.defineConstant("ALT_SEPARATOR", context.nil);
}
RubyString pathSeparator = RubyString.newString(runtime, singleCharByteList((byte) File.pathSeparatorChar));
pathSeparator.freeze(context);
fileClass.defineConstant("PATH_SEPARATOR", pathSeparator);
fileClass.getSingletonClass().defineAnnotatedMethods(RubyFileTest.FileTestFileMethods.class);
RubyModule constants = fileClass.defineModuleUnder("Constants");
constants.setConstant("RDONLY", runtime.newFixnum(OpenFlags.O_RDONLY.intValue()));
constants.setConstant("WRONLY", runtime.newFixnum(OpenFlags.O_WRONLY.intValue()));
constants.setConstant("RDWR", runtime.newFixnum(OpenFlags.O_RDWR.intValue()));
constants.setConstant("APPEND", runtime.newFixnum(OpenFlags.O_APPEND.intValue()));
constants.setConstant("CREAT", runtime.newFixnum(OpenFlags.O_CREAT.intValue()));
constants.setConstant("EXCL", runtime.newFixnum(OpenFlags.O_EXCL.intValue()));
if (
OpenFlags.O_NONBLOCK.defined()) {
if (!OpenFlags.O_NONBLOCK.defined()) {
}
constants.setConstant("NONBLOCK", runtime.newFixnum(OpenFlags.O_NONBLOCK.intValue()));
} else if (Platform.IS_WINDOWS) {
constants.setConstant("NONBLOCK", runtime.newFixnum(1));
}
constants.setConstant("TRUNC", runtime.newFixnum(OpenFlags.O_TRUNC.intValue()));
constants.setConstant("NOCTTY", runtime.newFixnum(OpenFlags.O_NOCTTY.intValue()));
if (!OpenFlags.O_BINARY.defined()) {
constants.setConstant("BINARY", runtime.newFixnum(0));
} else {
constants.setConstant("BINARY", runtime.newFixnum(OpenFlags.O_BINARY.intValue()));
}
constants.setConstant("SHARE_DELETE", runtime.newFixnum(0));
if (OpenFlags.O_SYNC.defined()) {
constants.setConstant("SYNC", runtime.newFixnum(OpenFlags.O_SYNC.intValue()));
}
if (OpenFlags.O_NOFOLLOW.defined()) {
constants.setConstant("NOFOLLOW", runtime.newFixnum(OpenFlags.O_NOFOLLOW.intValue()));
}
if (OpenFlags.O_TMPFILE.defined()) {
constants.setConstant("TMPFILE", runtime.newFixnum(OpenFlags.O_TMPFILE.intValue()));
}
constants.setConstant("FNM_NOESCAPE", runtime.newFixnum(FNM_NOESCAPE));
constants.setConstant("FNM_CASEFOLD", runtime.newFixnum(FNM_CASEFOLD));
constants.setConstant("FNM_SYSCASE", runtime.newFixnum(FNM_SYSCASE));
constants.setConstant("FNM_DOTMATCH", runtime.newFixnum(FNM_DOTMATCH));
constants.setConstant("FNM_PATHNAME", runtime.newFixnum(FNM_PATHNAME));
constants.setConstant("FNM_EXTGLOB", runtime.newFixnum(FNM_EXTGLOB));
constants.setConstant("LOCK_SH", runtime.newFixnum(RubyFile.LOCK_SH));
constants.setConstant("LOCK_EX", runtime.newFixnum(RubyFile.LOCK_EX));
constants.setConstant("LOCK_NB", runtime.newFixnum(RubyFile.LOCK_NB));
constants.setConstant("LOCK_UN", runtime.newFixnum(RubyFile.LOCK_UN));
constants.setConstant("NULL", runtime.newString(getNullDevice()));
runtime.getIO().includeModule(constants);
if (Platform.IS_WINDOWS) {
fileClass.searchMethod("readlink").setNotImplemented(true);
fileClass.searchMethod("mkfifo").setNotImplemented(true);
}
if (!Platform.IS_BSD) {
fileClass.getSingletonClass().searchMethod("lchmod").setNotImplemented(true);
}
return fileClass;
}
private static final ObjectAllocator FILE_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyFile(runtime, klass);
}
};
private static String getNullDevice() {
String null_device;
if (Platform.IS_WINDOWS) {
null_device = "NUL";
} else {
null_device = "/dev/null";
}
return null_device;
}
public RubyFile(Ruby runtime, RubyClass type) {
super(runtime, type);
}
RubyFile(Ruby runtime, String path, final Reader reader) {
this(runtime, path, new InputStream() {
@Override
public int read() throws IOException {
return reader.read();
}
});
}
public RubyFile(Ruby runtime, String path, InputStream in) {
super(runtime, runtime.getFile(), Channels.newChannel(in));
this.setPath(path);
}
public RubyFile(Ruby runtime, String path, FileChannel channel) {
super(runtime, runtime.getFile(), channel);
this.setPath(path);
}
@Override
protected IRubyObject rbIoClose(ThreadContext context) {
if (openFile.currentLock != null) {
try {
openFile.currentLock.release();
} catch (IOException e) {
throw context.runtime.newIOError(e.getMessage());
}
}
return super.rbIoClose(context);
}
@JRubyMethod(required = 1)
public IRubyObject flock(ThreadContext context, IRubyObject operation) {
if (Platform.IS_SOLARIS) {
return callMethod(context, "flock", operation);
}
Ruby runtime = context.runtime;
OpenFile fptr;
int op1;
op1 = RubyNumeric.num2int(operation);
fptr = getOpenFileChecked();
if (fptr.isWritable()) {
flushRaw(context, false);
}
while (fptr.threadFlock(context, op1) < 0) {
switch (fptr.errno()) {
case EAGAIN:
case EACCES:
case EWOULDBLOCK:
if ((op1 & LOCK_NB) != 0) return runtime.getFalse();
try {
context.getThread().sleep(100);
} catch (InterruptedException ie) {
context.pollThreadEvents();
}
fptr.checkClosed();
continue;
case EINTR:
break;
default:
throw runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath());
}
}
return RubyFixnum.zero(context.runtime);
}
@JRubyMethod(name = "initialize", required = 1, optional = 3, visibility = PRIVATE)
public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) {
if (openFile != null) {
throw context.runtime.newRuntimeError("reinitializing File");
}
if (args.length > 0 && args.length <= 3) {
IRubyObject fd = TypeConverter.convertToTypeWithCheck(context, args[0], context.runtime.getFixnum(), sites(context).to_int_checked);
if (!fd.isNil()) {
if (args.length == 1) {
return super.initialize(context, fd, block);
} else if (args.length == 2) {
return super.initialize(context, fd, args[1], block);
}
return super.initialize(context, fd, args[1], args[2], block);
}
}
return openFile(context, args);
}
@JRubyMethod(required = 1)
public IRubyObject chmod(ThreadContext context, IRubyObject arg) {
checkClosed(context);
int mode = (int) arg.convertToInteger().getLongValue();
final String path = getPath();
if ( ! new File(path).exists() ) {
throw context.runtime.newErrnoENOENTError(path);
}
return context.runtime.newFixnum(context.runtime.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);
}
final String path = getPath();
if ( ! new File(path).exists() ) {
throw context.runtime.newErrnoENOENTError(path);
}
return context.runtime.newFixnum(context.runtime.getPosix().chown(path, owner, group));
}
@JRubyMethod
public IRubyObject atime(ThreadContext context) {
checkClosed(context);
return context.runtime.newFileStat(getPath(), false).atime();
}
@JRubyMethod(name = "ctime")
public IRubyObject ctime(ThreadContext context) {
checkClosed(context);
return context.runtime.newFileStat(getPath(), false).ctime();
}
@JRubyMethod(name = "birthtime")
public IRubyObject birthtime(ThreadContext context) {
checkClosed(context);
FileTime btime = getBirthtimeWithNIO(getPath());
if (btime != null) return context.runtime.newTime(btime.toMillis());
return ctime(context);
}
public static final FileTime getBirthtimeWithNIO(String pathString) {
Path path = Paths.get(pathString);
PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
try {
if (view != null) {
return view.readAttributes().creationTime();
}
} catch (IOException ioe) {
}
return null;
}
@JRubyMethod
public IRubyObject lstat(ThreadContext context) {
checkClosed(context);
return context.runtime.newFileStat(getPath(), true);
}
@JRubyMethod
public IRubyObject mtime(ThreadContext context) {
checkClosed(context);
return ((RubyFileStat) stat(context)).mtime();
}
@JRubyMethod(meta = true)
public static IRubyObject path(ThreadContext context, IRubyObject self, IRubyObject str) {
return StringSupport.checkEmbeddedNulls(context.runtime, get_path(context, str));
}
@JRubyMethod(name = {"path", "to_path"})
public IRubyObject path(ThreadContext context) {
if ((openFile.getMode() & OpenFile.TMPFILE) != 0) {
throw context.runtime.newIOError("File is unnamed (TMPFILE?)");
}
final String path = getPath();
if (path != null) {
RubyString newPath = context.runtime.newString(path);
newPath.setTaint(true);
return newPath;
}
return context.nil;
}
@JRubyMethod(required = 1)
public IRubyObject truncate(ThreadContext context, IRubyObject len) {
Ruby runtime = context.runtime;
OpenFile fptr;
long pos;
pos = RubyNumeric.num2int(len);
fptr = getOpenFileChecked();
if (!fptr.isWritable()) {
throw runtime.newIOError("not opened for writing");
}
flushRaw(context, false);
if (pos < 0) {
throw runtime.newErrnoEINVALError(openFile.getPath());
}
if (fptr.posix.ftruncate(fptr.fd(), pos) < 0) {
throw runtime.newErrnoFromErrno(fptr.posix.getErrno(), fptr.getPath());
}
return RubyFixnum.zero(runtime);
}
@Override
public final IRubyObject inspect() {
return inspect(metaClass.runtime.getCurrentContext());
}
@JRubyMethod
public RubyString inspect(ThreadContext context) {
final String path = openFile.getPath();
ByteList str = new ByteList((path == null ? 4 : path.length()) + 8);
str.append('#').append('<');
str.append(getMetaClass().getRealClass().to_s().getByteList());
str.append(':').append(path == null ? RubyNil.nilBytes : RubyEncoding.encodeUTF8(path));
if (!openFile.isOpen()) {
str.append(' ').append('(');
str.append('c').append('l').append('o').append('s').append('e').append('d');
str.append(')');
}
str.append('>');
str.setEncoding(UTF8Encoding.INSTANCE);
return RubyString.newStringLight(context.runtime, str);
}
private static final String URI_PREFIX_STRING = "^(uri|jar|file|classpath):([^:/]{2,}:([^:/]{2,}:)?)?";
private static final Pattern ROOT_PATTERN = Pattern.compile(URI_PREFIX_STRING + "/?/?$");
private static final int NULL_CHAR = '\0';
@JRubyMethod(meta = true)
public static RubyString basename(ThreadContext context, IRubyObject recv, IRubyObject path) {
return basenameImpl(context, (RubyClass) recv, path, null);
}
@JRubyMethod(meta = true)
public static RubyString basename(ThreadContext context, IRubyObject recv, IRubyObject path, IRubyObject ext) {
return basenameImpl(context, (RubyClass) recv, path, ext == context.nil ? null : ext);
}
@Deprecated
public static IRubyObject basename(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
IRubyObject ext = (args.length > 1 && args[1] != context.nil) ? args[1] : null;
return basenameImpl(context, (RubyClass) recv, args[0], ext);
}
private static RubyString basenameImpl(ThreadContext context, RubyClass klass, IRubyObject path, IRubyObject ext) {
final Ruby runtime = context.runtime;
final int separatorChar = getSeparatorChar(klass);
final int altSeparatorChar = getAltSeparatorChar(klass);
RubyString origString = StringSupport.checkEmbeddedNulls(runtime, get_path(context, path));
Encoding origEncoding = origString.getEncoding();
String name = origString.toString();
if (name.endsWith(".jar!/") || ROOT_PATTERN.matcher(name).matches()) return (RubyString) path;
if (Platform.IS_WINDOWS) {
if (name.length() > 1 && name.charAt(1) == ':' && Character.isLetter(name.charAt(0))) {
switch (name.length()) {
case 2:
return (RubyString) RubyString.newEmptyString(runtime, origString.getEncoding()).infectBy(path);
case 3:
return RubyString.newString(runtime, RubyString.encodeBytelist(name.substring(2), origEncoding));
default:
switch (name.charAt(2)) {
case '/':
case '\\':
break;
default:
name = name.substring(2);
}
break;
}
}
}
while (name.length() > 1 && (name.charAt(name.length() - 1) == separatorChar || (name.charAt(name.length() - 1) == altSeparatorChar))) {
name = name.substring(0, name.length() - 1);
}
int slashCount = 0;
int length = name.length();
for (int i = length - 1; i >= 0; i--) {
char c = name.charAt(i);
if (c != separatorChar && c != altSeparatorChar) {
break;
}
slashCount++;
}
if (slashCount > 0 && length > 1) {
name = name.substring(0, name.length() - slashCount);
}
int index = name.lastIndexOf(separatorChar);
if (altSeparatorChar != NULL_CHAR) {
index = Math.max(index, name.lastIndexOf(altSeparatorChar));
}
if (!(contentEquals(name, separatorChar) || (contentEquals(name, altSeparatorChar))) && index != -1) {
name = name.substring(index + 1);
}
if (ext != null) {
final String extStr = RubyString.stringValue(ext).toString();
if (".*".equals(extStr)) {
index = name.lastIndexOf('.');
if (index > 0) {
name = name.substring(0, index);
}
} else if (name.endsWith(extStr)) {
name = name.substring(0, name.length() - extStr.length());
}
}
return RubyString.newString(runtime, RubyString.encodeBytelist(name, origEncoding));
}
private static int getSeparatorChar(final RubyClass File) {
final RubyString sep = RubyString.stringValue(File.getConstant("SEPARATOR"));
return sep.getByteList().get(0);
}
private static int getAltSeparatorChar(final RubyClass File) {
final IRubyObject sep = File.getConstant("ALT_SEPARATOR");
if (sep instanceof RubyString) {
return ((RubyString) sep).getByteList().get(0);
}
return NULL_CHAR;
}
@JRubyMethod(required = 2, rest = true, meta = true)
public static IRubyObject chmod(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;
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());
}
if (0 != runtime.getPosix().chmod(filename.getAbsolutePath(), (int) mode.getLongValue())) {
throw runtime.newErrnoFromLastPOSIXErrno();
} else {
count++;
}
}
return runtime.newFixnum(count);
}
@JRubyMethod(required = 2, rest = true, meta = true)
public static IRubyObject chown(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;
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());
}
if (0 != runtime.getPosix().chown(filename.getAbsolutePath(), owner, group)) {
throw runtime.newErrnoFromLastPOSIXErrno();
} else {
count++;
}
}
return runtime.newFixnum(count);
}
@JRubyMethod(required = 1, meta = true)
public static IRubyObject dirname(ThreadContext context, IRubyObject recv, IRubyObject arg) {
Ruby runtime = context.runtime;
RubyString filename = StringSupport.checkEmbeddedNulls(runtime, get_path(context, arg));
String jfilename = filename.asJavaString();
return runtime.newString(dirname(context, jfilename)).infectBy(filename);
}
static final Pattern PROTOCOL_PATTERN = Pattern.compile(URI_PREFIX_STRING + ".*");
public static String dirname(ThreadContext context, final String filename) {
final RubyClass File = context.runtime.getFile();
IRubyObject sep = File.getConstant("SEPARATOR");
final String separator; final char separatorChar;
if (sep instanceof RubyString && ((RubyString) sep).size() == 1) {
separatorChar = ((RubyString) sep).getByteList().charAt(0);
separator = (separatorChar == '/') ? "/" : String.valueOf(separatorChar);
}
else {
separator = sep.toString();
separatorChar = separator.isEmpty() ? '\0' : separator.charAt(0);
}
String altSeparator = null; char altSeparatorChar = '\0';
final IRubyObject rbAltSeparator = File.getConstantNoConstMissing("ALT_SEPARATOR");
if (rbAltSeparator != null && rbAltSeparator != context.nil) {
altSeparator = rbAltSeparator.toString();
if (!altSeparator.isEmpty()) altSeparatorChar = altSeparator.charAt(0);
}
String name = filename;
if (altSeparator != null) name = replace(filename, altSeparator, separator);
boolean startsWithSeparator = false;
if (!name.isEmpty()) startsWithSeparator = name.charAt(0) == separatorChar;
int idx;
if ((idx = name.indexOf(".jar!")) != -1 && name.startsWith(separator, idx + 5)) {
int start = name.indexOf("!" + separator) + 1;
String path = dirname(context, name.substring(start));
if (path.equals(".") || path.equals(separator)) path = "";
return name.substring(0, start) + path;
}
if (PROTOCOL_PATTERN.matcher(name).matches()) {
int start = name.indexOf(":" + separator) + 2;
String path = dirname(context, name.substring(start));
if (path.equals(".")) path = "";
return name.substring(0, start) + path;
}
int minPathLength = 1;
boolean trimmedSlashes = false;
boolean startsWithUNCOnWindows = Platform.IS_WINDOWS && startsWith(name, separatorChar, separatorChar);
if (startsWithUNCOnWindows) minPathLength = 2;
boolean startsWithDriveLetterOnWindows = startsWithDriveLetterOnWindows(name);
if (startsWithDriveLetterOnWindows) minPathLength = 3;
while (name.length() > minPathLength && name.charAt(name.length() - 1) == separatorChar) {
trimmedSlashes = true;
name = name.substring(0, name.length() - 1);
}
String result;
if (startsWithDriveLetterOnWindows && name.length() == 2) {
if (trimmedSlashes) {
result = filename.substring(0, 3);
} else {
result = filename.substring(0, 2) + '.';
}
} else {
int index = name.lastIndexOf(separator);
if (index == -1) {
if (startsWithDriveLetterOnWindows) {
return filename.substring(0, 2) + '.';
}
return ".";
}
if (index == 0) {
return filename.substring(0, 1);
}
if (startsWithDriveLetterOnWindows && index == 2) {
index++;
}
if (startsWithUNCOnWindows) {
index = filename.length();
List<String> split = StringSupport.split(name, separatorChar);
int pathSectionCount = 0;
for (int i = 0; i < split.size(); i++) {
if (!split.get(i).isEmpty()) pathSectionCount += 1;
}
if (pathSectionCount > 2) index = name.lastIndexOf(separator);
}
result = filename.substring(0, index);
}
if (startsWithSeparator && result.length() > minPathLength) {
while ( result.length() > minPathLength &&
(result.charAt(minPathLength) == separatorChar ||
(altSeparator != null && result.charAt(minPathLength) == altSeparatorChar)) ) {
result = result.substring(1, result.length());
}
}
char endChar;
while (result.length() > minPathLength) {
endChar = result.charAt(result.length() - 1);
if (endChar == separatorChar || (altSeparator != null && endChar == altSeparatorChar)) {
result = result.substring(0, result.length() - 1);
} else {
break;
}
}
return result;
}
@JRubyMethod(required = 1, meta = true)
public static IRubyObject extname(ThreadContext context, IRubyObject recv, IRubyObject arg) {
String filename = basename(context, recv, arg).getUnicodeValue();
int dotIndex = filename.lastIndexOf('.');
if (dotIndex > 0 && dotIndex != (filename.length() - 1)) {
return RubyString.newString(context.runtime, filename.substring(dotIndex));
}
return RubyString.newEmptyString(context.runtime);
}
@JRubyMethod(name = "expand_path", required = 1, optional = 1, meta = true)
public static IRubyObject expand_path(ThreadContext context, IRubyObject recv, IRubyObject... args) {
return expandPathInternal(context, args, true, false);
}
@Deprecated
public static IRubyObject expand_path19(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
return expand_path(context, recv, args);
}
@JRubyMethod(required = 1, optional = 1, meta = true)
public static IRubyObject absolute_path(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
return expandPathInternal(context, args, false, false);
}
@JRubyMethod(required = 1, optional = 1, meta = true)
public static IRubyObject realdirpath(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
return expandPathInternal(context, args, false, true);
}
public static IRubyObject realpath(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
RubyString file = expandPathInternal(context, args, false, true);
if (!RubyFileTest.exist(context, file)) {
throw context.runtime.newErrnoENOENTError(file.toString());
}
return file;
}
@JRubyMethod(meta = true)
public static IRubyObject realpath(ThreadContext context, IRubyObject recv, IRubyObject path) {
RubyString file;
file = StringSupport.checkEmbeddedNulls(context.runtime, get_path(context, path));
file = expandPathInternal(context, file, null, false, true);
if (!RubyFileTest.exist(context, file)) {
throw context.runtime.newErrnoENOENTError(file.toString());
}
return file;
}
@JRubyMethod(meta = true)
public static IRubyObject realpath(ThreadContext context, IRubyObject recv, IRubyObject path, IRubyObject cwd) {
RubyString file;
file = StringSupport.checkEmbeddedNulls(context.runtime, get_path(context, path));
RubyString wd = StringSupport.checkEmbeddedNulls(context.runtime, get_path(context, cwd));
file = expandPathInternal(context, file, wd, false, true);
if (!RubyFileTest.exist(context, file)) {
throw context.runtime.newErrnoENOENTError(file.toString());
}
return file;
}
@JRubyMethod(name = {"fnmatch", "fnmatch?"}, required = 2, optional = 1, meta = true)
public static IRubyObject fnmatch(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;
int flags = args.length == 3 ? RubyNumeric.num2int(args[2]) : 0;
boolean braces_match = false;
boolean extglob = (flags & FNM_EXTGLOB) != 0;
ByteList pattern = StringSupport.checkEmbeddedNulls(runtime, args[0].convertToString()).getByteList();
ByteList path = StringSupport.checkEmbeddedNulls(runtime, get_path(context, args[1])).getByteList();
if(extglob) {
String spattern = args[0].asJavaString();
ArrayList<String> patterns = org.jruby.util.Dir.braces(spattern, flags, new ArrayList<String>());
ArrayList<Boolean> matches = new ArrayList<Boolean>();
for(int i = 0; i < patterns.size(); i++) {
String p = patterns.get(i);
boolean match = dir_fnmatch(new ByteList(p.getBytes()), path, flags);
matches.add(match);
}
braces_match = matches.contains(true);
}
if(braces_match || dir_fnmatch(pattern, path, flags)) {
return runtime.getTrue();
}
return runtime.getFalse();
}
private static boolean dir_fnmatch(ByteList pattern, ByteList path, int flags) {
return org.jruby.util.Dir.fnmatch(pattern.getUnsafeBytes(),
pattern.getBegin(),
pattern.getBegin()+pattern.getRealSize(),
path.getUnsafeBytes(),
path.getBegin(),
path.getBegin()+path.getRealSize(),
flags) == 0;
}
@JRubyMethod(name = "ftype", required = 1, meta = true)
public static IRubyObject ftype(ThreadContext context, IRubyObject recv, IRubyObject filename) {
Ruby runtime = context.runtime;
RubyString path = StringSupport.checkEmbeddedNulls(runtime, get_path(context, filename));
return runtime.newFileStat(path.getUnicodeValue(), true).ftype();
}
@JRubyMethod(rest = true, meta = true)
public static RubyString join(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
return doJoin(context, recv, args);
}
@JRubyMethod(name = "lstat", required = 1, meta = true)
public static IRubyObject lstat(ThreadContext context, IRubyObject recv, IRubyObject filename) {
Ruby runtime = context.runtime;
String f = StringSupport.checkEmbeddedNulls(runtime, get_path(context, filename)).getUnicodeValue();
return runtime.newFileStat(f, true);
}
@JRubyMethod(name = "stat", required = 1, meta = true)
public static IRubyObject stat(ThreadContext context, IRubyObject recv, IRubyObject filename) {
Ruby runtime = context.runtime;
String f = StringSupport.checkEmbeddedNulls(runtime, get_path(context, filename)).getUnicodeValue();
return runtime.newFileStat(f, false);
}
@JRubyMethod(name = "atime", required = 1, meta = true)
public static IRubyObject atime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
Ruby runtime = context.runtime;
String f = StringSupport.checkEmbeddedNulls(runtime, get_path(context, filename)).getUnicodeValue();
return runtime.newFileStat(f, false).atime();
}
@JRubyMethod(name = "ctime", required = 1, meta = true)
public static IRubyObject ctime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
Ruby runtime = context.runtime;
String f = StringSupport.checkEmbeddedNulls(runtime, get_path(context, filename)).getUnicodeValue();
return runtime.newFileStat(f, false).ctime();
}
@JRubyMethod(name = "birthtime", required = 1, meta = true)
public static IRubyObject birthtime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
Ruby runtime = context.runtime;
String f = StringSupport.checkEmbeddedNulls(runtime, get_path(context, filename)).getUnicodeValue();
return runtime.newFileStat(f, false).birthtime();
}
@JRubyMethod(required = 1, rest = true, meta = true)
public static IRubyObject lchmod(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;
int count = 0;
RubyInteger mode = args[0].convertToInteger();
for (int i = 1; i < args.length; i++) {
JRubyFile file = file(args[i]);
if (0 != runtime.getPosix().lchmod(file.toString(), (int) mode.getLongValue())) {
throw runtime.newErrnoFromLastPOSIXErrno();
} else {
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.runtime;
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++) {
JRubyFile file = file(args[i]);
if (0 != runtime.getPosix().lchown(file.toString(), owner, group)) {
throw runtime.newErrnoFromLastPOSIXErrno();
} else {
count++;
}
}
return runtime.newFixnum(count);
}
@JRubyMethod(required = 2, meta = true)
public static IRubyObject link(ThreadContext context, IRubyObject recv, IRubyObject from, IRubyObject to) {
Ruby runtime = context.runtime;
String fromStr = file(from).toString();
String toStr = file(to).toString();
int ret = runtime.getPosix().link(fromStr, toStr);
if (ret != 0) {
if (runtime.getPosix().isNative()) {
throw runtime.newErrnoFromInt(runtime.getPosix().errno(), String.format("(%s, %s)", fromStr, toStr));
} else {
throw runtime.newErrnoEEXISTError(fromStr + " or " + toStr);
}
}
return runtime.newFixnum(ret);
}
@JRubyMethod(required = 1, meta = true)
public static IRubyObject mtime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
Ruby runtime = context.runtime;
String f = StringSupport.checkEmbeddedNulls(runtime, get_path(context, filename)).getUnicodeValue();
return runtime.newFileStat(f, false).mtime();
}
@JRubyMethod(required = 2, meta = true)
public static IRubyObject rename(ThreadContext context, IRubyObject recv, IRubyObject oldName, IRubyObject newName) {
Ruby runtime = context.runtime;
RubyString oldNameString = StringSupport.checkEmbeddedNulls(runtime, get_path(context, oldName));
RubyString newNameString = StringSupport.checkEmbeddedNulls(runtime, get_path(context, newName));
String newNameJavaString = newNameString.getUnicodeValue();
String oldNameJavaString = oldNameString.getUnicodeValue();
JRubyFile oldFile = JRubyFile.create(runtime.getCurrentDirectory(), oldNameJavaString);
JRubyFile newFile = JRubyFile.create(runtime.getCurrentDirectory(), newNameJavaString);
boolean isOldSymlink = RubyFileTest.symlink_p(recv, oldNameString).isTrue();
if (!(oldFile.exists() || isOldSymlink) || !newFile.getParentFile().exists()) {
throw runtime.newErrnoENOENTError(oldNameJavaString + " or " + newNameJavaString);
}
JRubyFile dest = JRubyFile.create(runtime.getCurrentDirectory(), newNameJavaString);
if (oldFile.renameTo(dest)) {
return RubyFixnum.zero(runtime);
}
if (newFile.exists()) {
runtime.getPosix().chmod(newNameJavaString, 0666);
newFile.delete();
}
Path oldPath = Paths.get(oldFile.toURI());
Path destPath = Paths.get(dest.getAbsolutePath());
try {
Files.move(oldPath, destPath, StandardCopyOption.ATOMIC_MOVE);
return RubyFixnum.zero(runtime);
} catch (IOException ioe) {
throw Helpers.newIOErrorFromException(runtime, ioe);
}
}
@JRubyMethod(required = 1, meta = true)
public static RubyArray split(ThreadContext context, IRubyObject recv, IRubyObject arg) {
Ruby runtime = context.runtime;
RubyString filename = StringSupport.checkEmbeddedNulls(runtime, get_path(context, arg));
return runtime.newArray(dirname(context, recv, filename), basename(context, recv, filename));
}
@JRubyMethod(required = 2, meta = true)
public static IRubyObject symlink(ThreadContext context, IRubyObject recv, IRubyObject from, IRubyObject to) {
Ruby runtime = context.runtime;
RubyString fromStr = StringSupport.checkEmbeddedNulls(runtime, get_path(context, from));
RubyString toStr = StringSupport.checkEmbeddedNulls(runtime, get_path(context, to));
String tovalue = toStr.getUnicodeValue();
tovalue = JRubyFile.create(runtime.getCurrentDirectory(), tovalue).getAbsolutePath();
try {
if (runtime.getPosix().symlink(fromStr.getUnicodeValue(), tovalue) == -1) {
if (runtime.getPosix().isNative()) {
throw runtime.newErrnoFromInt(runtime.getPosix().errno(), String.format("(%s, %s)", fromStr, toStr));
} else {
throw runtime.newErrnoEEXISTError(String.format("(%s, %s)", fromStr, toStr));
}
}
} catch (java.lang.UnsatisfiedLinkError ule) {
throw runtime.newNotImplementedError("symlink() function is unimplemented on this machine");
}
return RubyFixnum.zero(runtime);
}
@JRubyMethod(required = 1, meta = true)
public static IRubyObject readlink(ThreadContext context, IRubyObject recv, IRubyObject path) {
Ruby runtime = context.runtime;
if (Platform.IS_WINDOWS) {
throw runtime.newNotImplementedError("readlink");
}
JRubyFile link = file(path);
try {
String realPath = runtime.getPosix().readlink(link.toString());
if (realPath == null) {
throw runtime.newErrnoFromLastPOSIXErrno();
}
return RubyString.newString(runtime, realPath, runtime.getEncodingService().getFileSystemEncoding());
} catch (IOException e) {
throw runtime.newIOError(e.getMessage());
}
}
public static IRubyObject truncate(ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
return truncate19(context, recv, arg1, arg2);
}
@JRubyMethod(name = "truncate", required = 2, meta = true)
public static IRubyObject truncate19(ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
RubyString path = StringSupport.checkEmbeddedNulls(context.runtime, get_path(context, arg1));
return truncateCommon(context, recv, path, arg2);
}
@JRubyMethod(meta = true, optional = 1)
public static IRubyObject umask(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;
int oldMask;
if (args.length == 0) {
oldMask = PosixShim.umask(runtime.getPosix());
} else if (args.length == 1) {
int newMask = (int) args[0].convertToInteger().getLongValue();
oldMask = PosixShim.umask(runtime.getPosix(), newMask);
} else {
throw runtime.newArgumentError("wrong number of arguments");
}
return runtime.newFixnum(oldMask);
}
@JRubyMethod(required = 2, rest = true, meta = true)
public static IRubyObject lutime(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;
long[] atimeval = null;
long[] mtimeval = null;
if (args[0] != context.nil || args[1] != context.nil) {
atimeval = extractTimespec(context, args[0]);
mtimeval = extractTimespec(context, args[1]);
}
for (int i = 2, j = args.length; i < j; i++) {
RubyString filename = StringSupport.checkEmbeddedNulls(runtime, get_path(context, args[i]));
JRubyFile fileToTouch = JRubyFile.create(runtime.getCurrentDirectory(), filename.getUnicodeValue());
if (!fileToTouch.exists()) {
throw runtime.newErrnoENOENTError(filename.toString());
}
int result = runtime.getPosix().lutimes(fileToTouch.getAbsolutePath(), atimeval, mtimeval);
if (result == -1) {
throw runtime.newErrnoFromInt(runtime.getPosix().errno());
}
}
return runtime.newFixnum(args.length - 2);
}
@JRubyMethod(required = 2, rest = true, meta = true)
public static IRubyObject utime(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;
long[] atimespec = null;
long[] mtimespec = null;
if (args[0] != context.nil || args[1] != context.nil) {
atimespec = extractTimespec(context, args[0]);
mtimespec = extractTimespec(context, args[1]);
}
for (int i = 2, j = args.length; i < j; i++) {
RubyString filename = StringSupport.checkEmbeddedNulls(runtime, get_path(context, args[i]));
JRubyFile fileToTouch = JRubyFile.create(runtime.getCurrentDirectory(),filename.getUnicodeValue());
if (!fileToTouch.exists()) {
throw runtime.newErrnoENOENTError(filename.toString());
}
int result;
try {
result = runtime.getPosix().utimensat(0, fileToTouch.getAbsolutePath(), atimespec, mtimespec, 0);
} catch (NotImplementedError re) {
result = runtime.getPosix().utimes(fileToTouch.getAbsolutePath(), atimespec, mtimespec);
long[] atimeval = convertTimespecToTimeval(atimespec);
long[] mtimeval = convertTimespecToTimeval(mtimespec);
result = runtime.getPosix().utimes(fileToTouch.getAbsolutePath(), atimeval, mtimeval);
}
if (result == -1) {
throw runtime.newErrnoFromInt(runtime.getPosix().errno());
}
}
return runtime.newFixnum(args.length - 2);
}
@JRubyMethod(rest = true, meta = true)
public static IRubyObject delete(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;
for (int i = 0; i < args.length; i++) {
RubyString filename = StringSupport.checkEmbeddedNulls(runtime, get_path(context, args[i]));
JRubyFile file = JRubyFile.create(runtime.getCurrentDirectory(), filename.getUnicodeValue());
if (!file.exists() && !isSymlink(context, file)) {
throw runtime.newErrnoENOENTError(filename.getUnicodeValue());
}
if (file.isDirectory() && !isSymlink(context, file)) {
throw runtime.newErrnoEISDirError(filename.getUnicodeValue());
}
if (!file.delete()) {
throw runtime.newErrnoEACCESError(filename.getUnicodeValue());
}
}
return runtime.newFixnum(args.length);
}
private static boolean isSymlink(ThreadContext context, JRubyFile file) {
return FileResource.wrap(context.runtime.getPosix(), file).isSymLink();
}
@JRubyMethod(rest = true, meta = true)
public static IRubyObject unlink(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;
POSIX posix = runtime.getPosix();
if (!posix.isNative() || Platform.IS_WINDOWS) return delete(context, recv, args);
for (int i = 0; i < args.length; i++) {
RubyString filename = StringSupport.checkEmbeddedNulls(runtime, get_path(context, args[i]));
JRubyFile lToDelete = JRubyFile.create(runtime.getCurrentDirectory(), filename.getUnicodeValue());
boolean isSymlink = RubyFileTest.symlink_p(recv, filename).isTrue();
if (!lToDelete.exists() && !isSymlink) {
throw runtime.newErrnoENOENTError(filename.getUnicodeValue());
}
if (lToDelete.isDirectory() && !isSymlink) {
throw runtime.newErrnoEPERMError(filename.getUnicodeValue());
}
if (posix.unlink(lToDelete.getAbsolutePath()) < 0) {
throw runtime.newErrnoFromInt(posix.errno());
}
}
return runtime.newFixnum(args.length);
}
public static IRubyObject unlink(ThreadContext context, IRubyObject... args) {
return unlink(context, context.runtime.getFile(), args);
}
@JRubyMethod
public IRubyObject size(ThreadContext context) {
return RubyFixnum.newFixnum(context.runtime, getSize(context));
}
final long getSize(ThreadContext context) {
OpenFile fptr = getOpenFileChecked();
if ((fptr.getMode() & OpenFile.WRITABLE) != 0) {
flushRaw(context, false);
}
return fptr.posix.size(fptr.fd());
}
@JRubyMethod(meta = true)
public static IRubyObject mkfifo(ThreadContext context, IRubyObject recv, IRubyObject path) {
if (Platform.IS_WINDOWS) throw context.runtime.newNotImplementedError("mkfifo");
return mkfifo(context, get_path(context, path), 0666);
}
@JRubyMethod(meta = true)
public static IRubyObject mkfifo(ThreadContext context, IRubyObject recv, IRubyObject path, IRubyObject mode) {
if (Platform.IS_WINDOWS) throw context.runtime.newNotImplementedError("mkfifo");
return mkfifo(context, get_path(context, path), RubyNumeric.num2int(mode));
}
public static IRubyObject mkfifo(ThreadContext context, RubyString path, int mode) {
Ruby runtime = context.runtime;
String decodedPath = JRubyFile.createResource(runtime, path.toString()).absolutePath();
if (runtime.getPosix().mkfifo(decodedPath, mode) != 0) {
throw runtime.newErrnoFromInt(runtime.getPosix().errno(), decodedPath);
}
return RubyFixnum.zero(runtime);
}
public String getPath() {
if (openFile == null) return null;
return openFile.getPath();
}
private void setPath(String path) {
if (openFile == null) return;
openFile.setPath(path);
}
@Override
public Encoding getEncoding() {
return null;
}
@Override
public void setEncoding(Encoding encoding) {
}
protected IRubyObject openFile(ThreadContext context, IRubyObject args[]) {
Ruby runtime = context.runtime;
RubyString filename = StringSupport.checkEmbeddedNulls(runtime, get_path(context, args[0]));
setPath(adjustRootPathOnWindows(runtime, filename.asJavaString(), runtime.getCurrentDirectory()));
Object pm = EncodingUtils.vmodeVperm(null, null);
IRubyObject options = context.nil;
switch(args.length) {
case 1:
break;
case 2: {
IRubyObject test = TypeConverter.checkHashType(runtime, args[1]);
if (test instanceof RubyHash) {
options = test;
} else {
vmode(pm, args[1]);
}
break;
}
case 3: {
IRubyObject test = TypeConverter.checkHashType(runtime, args[2]);
if (test instanceof RubyHash) {
options = test;
} else {
vperm(pm, args[2]);
}
vmode(pm, args[1]);
break;
}
case 4:
if (!args[3].isNil()) {
options = TypeConverter.convertToTypeWithCheck(context, args[3], context.runtime.getHash(), sites(context).to_hash_checked);
if (options.isNil()) {
throw runtime.newArgumentError(4, 1, 3);
}
}
vperm(pm, args[2]);
vmode(pm, args[1]);
break;
}
int[] oflags_p = {0}, fmode_p = {0};
IOEncodable convconfig = new ConvConfig();
EncodingUtils.extractModeEncoding(context, convconfig, pm, options, oflags_p, fmode_p);
int perm = (vperm(pm) != null && !vperm(pm).isNil()) ?
RubyNumeric.num2int(vperm(pm)) : 0666;
return fileOpenGeneric(context, filename, oflags_p[0], fmode_p[0], convconfig, perm);
}
public IRubyObject fileOpenGeneric(ThreadContext context, IRubyObject filename, int oflags, int fmode, IOEncodable convConfig, int perm) {
if (convConfig == null) {
convConfig = new ConvConfig();
EncodingUtils.ioExtIntToEncs(context, convConfig, null, null, fmode);
convConfig.setEcflags(0);
convConfig.setEcopts(context.nil);
}
int[] fmode_p = {fmode};
EncodingUtils.validateEncodingBinmode(context, fmode_p, convConfig.getEcflags(), convConfig);
OpenFile fptr = MakeOpenFile();
fptr.setMode(fmode_p[0]);
fptr.encs.copy(convConfig);
fptr.setPath(adjustRootPathOnWindows(context.runtime,
RubyFile.get_path(context, filename).asJavaString(), getRuntime().getCurrentDirectory()));
fptr.setFD(sysopen(context.runtime, fptr.getPath(), oflags, perm));
fptr.checkTTY();
if ((fmode_p[0] & OpenFile.SETENC_BY_BOM) != 0) {
EncodingUtils.ioSetEncodingByBOM(context, this);
}
return this;
}
public static RubyString get_path(ThreadContext context, IRubyObject path) {
if (path instanceof RubyString) return StringSupport.checkEmbeddedNulls(context.runtime, path);
FileSites sites = sites(context);
if (sites.respond_to_to_path.respondsTo(context, path, path, true)) path = sites.to_path.call(context, path, path);
return filePathConvert(context, path.convertToString());
}
private static RubyString filePathConvert(ThreadContext context, RubyString path) {
StringSupport.checkEmbeddedNulls(context.runtime, path.convertToString());
if (!Platform.IS_WINDOWS) {
Ruby runtime = context.getRuntime();
EncodingService encodingService = runtime.getEncodingService();
Encoding pathEncoding = path.getEncoding();
if (runtime.getDefaultInternalEncoding() != null &&
pathEncoding != encodingService.getUSAsciiEncoding() &&
pathEncoding != encodingService.getAscii8bitEncoding() &&
pathEncoding != encodingService.getFileSystemEncoding() &&
!path.isAsciiOnly()) {
path = EncodingUtils.strConvEnc(context, path, pathEncoding, encodingService.getFileSystemEncoding());
}
}
return path;
}
public static FileResource fileResource(ThreadContext context, IRubyObject pathOrFile) {
Ruby runtime = context.runtime;
if (pathOrFile instanceof RubyFile) {
return JRubyFile.createResource(runtime, ((RubyFile) pathOrFile).getPath());
} else if (pathOrFile instanceof RubyIO) {
return JRubyFile.createResource(runtime, ((RubyIO) pathOrFile).openFile.getPath());
}
RubyString path = StringSupport.checkEmbeddedNulls(runtime, get_path(context, pathOrFile));
return JRubyFile.createResource(runtime, path.toString());
}
public static FileResource fileResource(IRubyObject pathOrFile) {
Ruby runtime = pathOrFile.getRuntime();
if (pathOrFile instanceof RubyIO) {
return JRubyFile.createResource(runtime, ((RubyIO) pathOrFile).getOpenFileChecked().getPath());
}
ThreadContext context = runtime.getCurrentContext();
RubyString path = StringSupport.checkEmbeddedNulls(runtime, get_path(context, pathOrFile));
return JRubyFile.createResource(runtime, path.toString());
}
@Deprecated
public static JRubyFile file(IRubyObject pathOrFile) {
return fileResource(pathOrFile).unwrap(JRubyFile.class);
}
@Override
public String toString() {
return "RubyFile(" + openFile.getPath() + ", " + openFile.getMode() + ')';
}
private static ZipEntry getFileEntry(ZipFile jar, String path, final String prefixForNoEntry) throws IOException {
ZipEntry entry = jar.getEntry(path);
if (entry == null) {
path = new File(path).getCanonicalPath().substring(prefixForNoEntry.length() + 1);
entry = jar.getEntry(StringSupport.replaceAll(path, "\\\\", "/").toString());
}
return entry;
}
@Deprecated
public static ZipEntry getDirOrFileEntry(String jar, String path) throws IOException {
return getDirOrFileEntry(new JarFile(jar), path);
}
@Deprecated
public static ZipEntry getDirOrFileEntry(ZipFile jar, String path) throws IOException {
String dirPath = path + '/';
ZipEntry entry = jar.getEntry(dirPath);
if (entry == null) {
if (dirPath.length() == 1) {
return new ZipEntry(dirPath);
}
final String prefix = new File(".").getCanonicalPath();
entry = jar.getEntry(new File(dirPath).getCanonicalPath().substring(prefix.length() + 1).replaceAll("\\\\", "/"));
if (entry == null) {
Enumeration<? extends ZipEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
String zipEntry = entries.nextElement().getName();
if (zipEntry.startsWith(dirPath)) {
return new ZipEntry(dirPath);
}
}
}
if (entry == null) {
entry = getFileEntry(jar, path, prefix);
}
}
return entry;
}
private static boolean isAbsolutePath(String path) {
return (path != null && path.length() > 1 && path.charAt(0) == '/') ||
startsWithDriveLetterOnWindows(path);
}
private static boolean isWindowsDriveLetter(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
public static boolean startsWithDriveLetterOnWindows(String path) {
return startsWithDriveLetterOnWindows((CharSequence) path);
}
static boolean startsWithDriveLetterOnWindows(CharSequence 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) == ':'));
}
static String adjustRootPathOnWindows(Ruby runtime, String path, String dir) {
if (path == null || !Platform.IS_WINDOWS) return path;
if ((startsWith(path, '/') && !(path.length() > 2 && path.charAt(2) == ':')) || startsWith(path, '\\')) {
if (path.length() > 1 && (path.charAt(1) == '/' || path.charAt(1) == '\\')) {
return path;
}
if (!startsWithDriveLetterOnWindows(dir)) {
dir = runtime.getCurrentDirectory();
}
if (dir.length() >= 2) {
path = dir.substring(0, 2) + path;
}
} else if (startsWithDriveLetterOnWindows(path) && path.length() == 2) {
path += '/';
}
return path;
}
private static long[] (ThreadContext context, IRubyObject value) {
long[] timespec = new long[2];
if (value instanceof RubyFloat) {
timespec[0] = Platform.IS_32_BIT ? RubyNumeric.num2int(value) : RubyNumeric.num2long(value);
double fraction = ((RubyFloat) value).getDoubleValue() % 1.0;
timespec[1] = (long)(fraction * 1e9 + 0.5);
} else if (value instanceof RubyNumeric) {
timespec[0] = Platform.IS_32_BIT ? RubyNumeric.num2int(value) : RubyNumeric.num2long(value);
timespec[1] = 0;
} else {
RubyTime time;
if (value instanceof RubyTime) {
time = ((RubyTime) value);
} else {
time = (RubyTime) TypeConverter.convertToType(context, value, context.runtime.getTime(), sites(context).to_time_checked, true);
}
timespec[0] = Platform.IS_32_BIT ? RubyNumeric.num2int(time.to_i()) : RubyNumeric.num2long(time.to_i());
timespec[1] = Platform.IS_32_BIT ? RubyNumeric.num2int(time.nsec()) : RubyNumeric.num2long(time.nsec());
}
return timespec;
}
private static long[] convertTimespecToTimeval(long[] timespec) {
if (timespec == null) {
return null;
}
long[] timeval = new long[2];
timeval[0] = timespec[0];
timeval[1] = timespec[1] / 1000;
return timeval;
}
private void checkClosed(ThreadContext context) {
openFile.checkClosed();
}
private static final Pattern PROTOCOL_PREFIX_PATTERN = Pattern.compile(URI_PREFIX_STRING);
private static RubyString expandPathInternal(ThreadContext context, IRubyObject[] args, boolean expandUser, boolean canonicalize) {
RubyString path = StringSupport.checkEmbeddedNulls(context.runtime, get_path(context, args[0]));
RubyString wd = null;
if (args.length == 2 && args[1] != context.nil) {
wd = StringSupport.checkEmbeddedNulls(context.runtime, get_path(context, args[1]));
}
return expandPathInternal(context, path, wd, expandUser, canonicalize);
}
static RubyString expandPathInternal(ThreadContext context, RubyString path, RubyString wd, boolean expandUser, boolean canonicalize) {
Ruby runtime = context.runtime;
String relativePath = path.getUnicodeValue();
Encoding enc = path.getEncoding();
Encoding fsenc = runtime.getEncodingService().getFileSystemEncoding();
if (Platform.IS_WINDOWS && ("NUL:".equalsIgnoreCase(relativePath) || "NUL".equalsIgnoreCase(relativePath))) {
return RubyString.newString(runtime, concat("//./", relativePath.substring(0, 3)), fsenc);
}
String preFix = "";
if (relativePath.startsWith("file:")) {
preFix = "file:";
relativePath = relativePath.substring(5);
}
String postFix = "";
Matcher protocol = PROTOCOL_PREFIX_PATTERN.matcher(relativePath);
if (relativePath.contains(".jar!/")) {
if (protocol.find()) {
preFix = protocol.group();
int extra = 0;
if (relativePath.contains("file://")) {
extra = 2;
preFix += "//";
}
relativePath = relativePath.substring(protocol.end() + extra);
}
int index = relativePath.indexOf("!/");
postFix = relativePath.substring(index);
relativePath = relativePath.substring(0, index);
} else if (protocol.find()) {
preFix = protocol.group();
int offset = protocol.end();
String extra = "";
int index = relativePath.indexOf("file://");
boolean classloaderURI = preFix.equals("uri:classloader:") || preFix.equals("classpath:");
if (index >= 0) {
index += 7;
if (relativePath.length() > index && relativePath.charAt(index) == '/') {
offset += 2; extra = "//";
} else {
offset += 1; extra = "/";
}
} else {
if (classloaderURI && relativePath.startsWith("//", offset)) {
offset += 1;
}
}
relativePath = relativePath.substring(offset);
if (classloaderURI) {
String fakePrefix = Platform.IS_WINDOWS ? "C:/FAKEPATH_PREFIX__" : "/FAKEPATH_PREFIX__";
relativePath = canonicalizePath(fakePrefix + '/' + relativePath).substring(fakePrefix.length());
} else {
relativePath = canonicalizePath(relativePath);
}
if (Platform.IS_WINDOWS) {
if (!preFix.contains("file:") && startsWithDriveLetterOnWindows(relativePath)) {
relativePath = relativePath.substring(2);
}
if (classloaderURI) {
relativePath = relativePath.replace('\\', '/');
}
}
return concatStrings(runtime, preFix, extra, relativePath, enc);
}
String[] uriParts = splitURI(relativePath);
if (expandUser && startsWith(relativePath, '~')) {
enc = fsenc;
relativePath = expandUserPath(context, relativePath, true);
}
if (uriParts != null) {
if (uriParts[0].equals("classpath:")) {
return concatStrings(runtime, preFix, relativePath, postFix, enc);
}
relativePath = uriParts[1];
}
String cwd;
if (wd != null) {
cwd = wd.toString();
enc = fsenc;
if (!cwd.startsWith("uri:")) {
if (expandUser) {
cwd = expandUserPath(context, cwd, true);
}
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) == '/';
if (!startsWithSlashNotOnWindows && !startsWithDriveLetterOnWindows(cwd)) {
if (cwd.length() == 0) cwd = ".";
cwd = JRubyFile.create(runtime.getCurrentDirectory(), cwd).getAbsolutePath();
}
}
} else {
cwd = runtime.getCurrentDirectory();
}
assert cwd != null;
String padSlashes = "";
if (uriParts != null) {
padSlashes = uriParts[0];
} else if (!Platform.IS_WINDOWS) {
if (relativePath.length() > 0 && relativePath.charAt(0) == '/') {
padSlashes = countSlashes(relativePath);
} else {
padSlashes = countSlashes(cwd);
}
}
JRubyFile pathFile;
if (relativePath.length() == 0) {
pathFile = JRubyFile.create(relativePath, cwd);
} else {
relativePath = adjustRootPathOnWindows(runtime, relativePath, cwd);
pathFile = JRubyFile.create(cwd, relativePath);
}
CharSequence canonicalPath = null;
if (Platform.IS_WINDOWS && uriParts != null && "classpath:".equals(uriParts[0])) {
String absolutePath = pathFile.getAbsolutePath();
if (absolutePath.length() >= 2 && absolutePath.charAt(1) == ':') {
canonicalPath = canonicalize(null, absolutePath.substring(2));
}
}
if (canonicalPath == null) canonicalPath = canonicalize(null, pathFile.getAbsolutePath());
CharSequence realPath;
if (padSlashes.isEmpty()) {
realPath = canonicalPath;
}
else {
realPath = concat(padSlashes, canonicalPath);
if (preFix.length() > 0 && padSlashes.startsWith("file:")) {
realPath = realPath.toString().substring(5);
}
}
if (canonicalize) realPath = canonicalNormalized(realPath);
if (postFix.contains("..")) postFix = adjustPostFixDotDot(postFix);
return concatStrings(runtime, preFix, realPath, postFix, enc);
}
private static String canonicalNormalized(CharSequence realPath) {
final String path = realPath.toString();
try {
return JRubyFile.normalizeSeps(new File(path).getCanonicalPath());
} catch (IOException ioe) {
return path;
}
}
private static String adjustPostFixDotDot(String postFix) {
postFix = '!' + canonicalizePath(postFix.substring(1));
if (Platform.IS_WINDOWS ) {
postFix = postFix.replace('\\', '/');
if (startsWithDriveLetterOnWindows(postFix.substring(1))) {
postFix = '!' + postFix.substring(3);
}
}
return postFix;
}
private static RubyString concatStrings(final Ruby runtime, String s1, CharSequence s2, String s3, Encoding enc) {
StringBuilder str =
new StringBuilder(s1.length() + s2.length() + s3.length()).append(s1).append(s2).append(s3);
return new RubyString(runtime, runtime.getString(), str, enc);
}
private static String canonicalizePath(String path) {
try {
return new File(path).getCanonicalPath();
}
catch (IOException ignore) { return path; }
}
public static String[] splitURI(String path) {
Matcher m = URI_PREFIX.matcher(path);
if (m.find()) {
if (m.group(2).length() == 0) {
return new String[] { path, "" };
}
String pathWithoutJarPrefix;
if (m.group(1) != null) {
pathWithoutJarPrefix = path.substring(4);
} else {
pathWithoutJarPrefix = path;
}
try {
URI u = new URI(pathWithoutJarPrefix);
String pathPart = u.getPath();
return new String[] { path.substring(0, path.indexOf(pathPart)), pathPart };
} catch (Exception e) {
try {
URL u = new URL(pathWithoutJarPrefix);
String pathPart = u.getPath();
return new String[] { path.substring(0, path.indexOf(pathPart)), pathPart };
} catch (MalformedURLException e2) { }
}
}
return null;
}
public static String expandUserPath(ThreadContext context, String path) {
return expandUserPath(context, path, false);
}
public static String expandUserPath(ThreadContext context, String path, final boolean raiseOnRelativePath) {
int pathLength = path.length();
if (startsWith(path, '~')) {
int userEnd = path.indexOf('/');
if (userEnd == -1) {
if (pathLength == 1) {
path = RubyDir.getHomeDirectoryPath(context, checkHome(context)).toString();
if (raiseOnRelativePath && !isAbsolutePath(path)) {
throw context.runtime.newArgumentError("non-absolute home");
}
} else {
userEnd = pathLength;
}
}
if (userEnd == 1) {
path = RubyDir.getHomeDirectoryPath(context, checkHome(context)).toString() + path.substring(1);
if (raiseOnRelativePath && !isAbsolutePath(path)) {
throw context.runtime.newArgumentError("non-absolute home");
}
} else if (userEnd > 1){
String user = path.substring(1, userEnd);
IRubyObject dir = RubyDir.getHomeDirectoryPath(context, user);
if (dir.isNil()) {
throw context.runtime.newArgumentError("user " + user + " does not exist");
}
path = dir + (pathLength == userEnd ? "" : path.substring(userEnd));
if (raiseOnRelativePath && !isAbsolutePath(path)) {
throw context.runtime.newArgumentError("non-absolute home of " + user);
}
}
}
return path;
}
private static String countSlashes(String stringToCheck) {
int slashCount = 0;
final int len = stringToCheck.length();
if (len > 0 && stringToCheck.charAt(0) == '/') {
if (len > 1 && stringToCheck.charAt(1) == '/') {
slashCount++;
for (int i = 2; i < len; i++) {
if (stringToCheck.charAt(i) == '/') {
slashCount++;
} else {
break;
}
}
}
}
if (slashCount < SLASHES.length) {
return SLASHES[slashCount];
}
char[] slashes = new char[slashCount];
for (int i = 0; i < slashCount; i++) {
slashes[i] = '/';
}
return new String(slashes);
}
public static String canonicalize(String path) {
CharSequence canonical = canonicalize(null, path);
return canonical == null ? null : canonical.toString();
}
private static CharSequence canonicalize(CharSequence canonicalPath, String remaining) {
if (remaining == null) {
if (canonicalPath == null) return null;
if (canonicalPath.length() == 0) return "/";
if (startsWithDriveLetterOnWindows(canonicalPath) && canonicalPath.length() == 2) {
return appendSlash(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);
}
CharSequence path = canonicalPath;
if (child.equals(".")) {
if (slash == -1) {
if (canonicalPath != null && canonicalPath.length() == 0) {
path = appendSlash(canonicalPath);
}
} else {
}
} else if (child.equals("..")) {
if (canonicalPath == null) throw new IllegalArgumentException("Cannot have .. at the start of an absolute path");
canonicalPath = canonicalPath.toString();
int lastDir = ((String) canonicalPath).lastIndexOf('/');
if (lastDir == -1) {
if (startsWithDriveLetterOnWindows(canonicalPath)) {
} else {
path = "";
}
} else {
path = canonicalPath.subSequence(0, lastDir);
}
} else if (canonicalPath == null) {
path = child;
} else {
path = new StringBuilder(canonicalPath.length() + 1 + child.length()).
append(canonicalPath).append('/').append(child);
}
return canonicalize(path, remaining);
}
private static StringBuilder appendSlash(final CharSequence canonicalPath) {
return new StringBuilder(canonicalPath.length() + 1).append(canonicalPath).append('/');
}
private static RubyString checkHome(ThreadContext context) {
Ruby runtime = context.runtime;
IRubyObject home = runtime.getENV().fastARef(RubyString.newStringShared(runtime, RubyDir.HOME));
if (home == null || home == context.nil || ((RubyString) home).size() == 0) {
throw runtime.newArgumentError("couldn't find HOME environment -- expanding `~'");
}
return (RubyString) home;
}
@Override
public <T> T toJava(Class<T> target) {
if (target == File.class) {
final String path = getPath();
return path == null ? null : (T) new File(path);
}
if (target == Path.class || target == Watchable.class) {
final String path = getPath();
return path == null ? null : (T) FileSystems.getDefault().getPath(path);
}
return super.toJava(target);
}
private static RubyString doJoin(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
final Ruby runtime = context.runtime;
final String separator = runtime.getFile().getConstant("SEPARATOR").toString();
final RubyArray argsAry = RubyArray.newArrayMayCopy(runtime, args);
final StringBuilder buffer = new StringBuilder(24);
boolean isTainted = joinImpl(buffer, separator, context, recv, argsAry);
RubyString fixedStr = new RubyString(runtime, runtime.getString(), buffer);
fixedStr.setTaint(isTainted);
return fixedStr;
}
private static boolean joinImpl(final StringBuilder buffer, final String separator,
ThreadContext context, IRubyObject recv, RubyArray args) {
boolean isTainted = false;
for (int i = 0; i < args.size(); i++) {
final IRubyObject arg = args.eltInternal(i);
if (arg.isTaint()) isTainted = true;
final CharSequence element;
if (arg instanceof RubyString) {
element = arg.convertToString().getUnicodeValue();
} else if (arg instanceof RubyArray) {
if (context.runtime.isInspecting(arg)) {
throw context.runtime.newArgumentError("recursive array");
} else {
element = joinImplInspecting(separator, context, recv, args, ((RubyArray) arg));
}
} else {
RubyString path = StringSupport.checkEmbeddedNulls(context.runtime, get_path(context, arg));
element = path.getUnicodeValue();
}
int trailingDelimiterIndex = chomp(buffer);
boolean trailingDelimiter = trailingDelimiterIndex != -1;
boolean leadingDelimiter = element.length() > 0 && isDirSeparator(element.charAt(0));
if (i > 0) {
if (leadingDelimiter) {
if (trailingDelimiter) buffer.delete(trailingDelimiterIndex, buffer.length());
} else if (!trailingDelimiter) {
buffer.append(separator);
}
}
buffer.append(element);
}
return isTainted;
}
private static StringBuilder joinImplInspecting(final String separator,
ThreadContext context, IRubyObject recv, RubyArray parent, RubyArray array) {
final Ruby runtime = context.runtime;
final StringBuilder buffer = new StringBuilder(24);
if (runtime.isInspecting(parent)) {
joinImpl(buffer, separator, context, recv, array);
return buffer;
}
try {
runtime.registerInspecting(parent);
joinImpl(buffer, separator, context, recv, array);
return buffer;
}
finally {
runtime.unregisterInspecting(parent);
}
}
private static boolean isDirSeparator(char c) {
return c == '/' || Platform.IS_WINDOWS && c == '\\';
}
private static int chomp(final StringBuilder buffer) {
boolean found = false;
for (int lastIndex = buffer.length() - 1; lastIndex >= 0; lastIndex--) {
if (!isDirSeparator(buffer.charAt(lastIndex))) {
if (found) return lastIndex + 1;
break;
}
found = true;
}
return found ? 0 : -1;
}
private static String replace(final String str, CharSequence target, CharSequence replace) {
if (target.length() == 1 && replace.length() == 1) {
return str.replace(target.charAt(0), replace.charAt(0));
}
return str.replace(target, replace);
}
private static IRubyObject truncateCommon(ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
RubyString filename = arg1.convertToString();
Ruby runtime = context.runtime;
RubyInteger newLength = arg2.convertToInteger();
File testFile ;
File childFile = new File(filename.getUnicodeValue());
String filenameString = Helpers.decodeByteList(runtime, filename.getByteList());
if ( childFile.isAbsolute() ) {
testFile = childFile ;
} else {
testFile = new File(runtime.getCurrentDirectory(), filenameString);
}
if (!testFile.exists()) {
throw runtime.newErrnoENOENTError(filenameString);
}
if (newLength.getLongValue() < 0) {
throw runtime.newErrnoEINVALError(filenameString);
}
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);
}
private static FileSites sites(ThreadContext context) {
return context.sites.File;
}
@Deprecated
public IRubyObject initialize19(IRubyObject[] args, Block block) {
return initialize(null, args, block);
}
private static final long serialVersionUID = 1L;
public static final int LOCK_SH = PosixShim.LOCK_SH;
public static final int LOCK_EX = PosixShim.LOCK_EX;
public static final int LOCK_NB = PosixShim.LOCK_NB;
public static final int LOCK_UN = PosixShim.LOCK_UN;
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_EXTGLOB = 16;
private static final int FNM_SYSCASE = Platform.IS_WINDOWS ? FNM_CASEFOLD : 0;
private static final String[] SLASHES = { "", "/", "//" };
private static final Pattern URI_PREFIX = Pattern.compile("^(jar:)?[a-z]{2,}:(.*)");
@Deprecated
protected String path;
}